@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,186 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import { defineRelationship } from '../relationship-index';
|
|
4
|
+
import { Update } from '../schedule-token';
|
|
5
|
+
import { World } from '../world';
|
|
6
|
+
|
|
7
|
+
const Marker = defineComponent('NanPreflightMarker', {});
|
|
8
|
+
const Numeric = defineComponent('NanPreflightNumeric', {
|
|
9
|
+
scalar: 'f32',
|
|
10
|
+
values: 'array<f32>',
|
|
11
|
+
});
|
|
12
|
+
const InlineNumeric = defineComponent('NanPreflightInlineNumeric', {
|
|
13
|
+
scalar: 'f32',
|
|
14
|
+
values: 'array<f32, 2>',
|
|
15
|
+
});
|
|
16
|
+
const { source: ParentOf, target: ChildrenOf } = defineRelationship({
|
|
17
|
+
sourceName: 'NanPreflightParentOf',
|
|
18
|
+
sourceField: 'parent',
|
|
19
|
+
targetName: 'NanPreflightChildrenOf',
|
|
20
|
+
targetField: 'children',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
interface NumericValueError {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
readonly detail: {
|
|
26
|
+
readonly component: string;
|
|
27
|
+
readonly field: string;
|
|
28
|
+
readonly received: unknown;
|
|
29
|
+
readonly index?: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function expectNumericValueError(
|
|
34
|
+
error: unknown,
|
|
35
|
+
expected: { readonly field: string; readonly received: unknown; readonly index?: number },
|
|
36
|
+
): void {
|
|
37
|
+
expect(error).toBeDefined();
|
|
38
|
+
if (error === undefined) return;
|
|
39
|
+
const numericError = error as NumericValueError;
|
|
40
|
+
expect(numericError.code).toBe('component-numeric-value-invalid');
|
|
41
|
+
expect(numericError.detail).toMatchObject({
|
|
42
|
+
component: Numeric.name,
|
|
43
|
+
field: expected.field,
|
|
44
|
+
received: expected.received,
|
|
45
|
+
...(expected.index === undefined ? {} : { index: expected.index }),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('component NaN object-write preflight', () => {
|
|
50
|
+
it('rejects spawn scalar NaN before entity or relationship mutation', () => {
|
|
51
|
+
const world = new World();
|
|
52
|
+
const parent = world.spawn({ component: Marker, data: {} }).unwrap();
|
|
53
|
+
const beforeCount = world.inspect().entityCount;
|
|
54
|
+
const beforeEpoch = world.getStructureEpoch();
|
|
55
|
+
|
|
56
|
+
const result = world.spawn(
|
|
57
|
+
{ component: Numeric, data: { scalar: Number.NaN, values: [1, 2] } },
|
|
58
|
+
{ component: ParentOf, data: { parent } },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(result.ok).toBe(false);
|
|
62
|
+
if (!result.ok)
|
|
63
|
+
expectNumericValueError(result.error, { field: 'scalar', received: Number.NaN });
|
|
64
|
+
expect(world.inspect().entityCount).toBe(beforeCount);
|
|
65
|
+
expect(world.getStructureEpoch()).toBe(beforeEpoch);
|
|
66
|
+
expect(world.get(parent, ChildrenOf).ok).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects addComponent array NaN before archetype or managed-array mutation', () => {
|
|
70
|
+
const world = new World();
|
|
71
|
+
const entity = world.spawn({ component: Marker, data: {} }).unwrap();
|
|
72
|
+
const beforeEpoch = world.getStructureEpoch();
|
|
73
|
+
|
|
74
|
+
const result = world.addComponent(entity, {
|
|
75
|
+
component: Numeric,
|
|
76
|
+
data: { scalar: 3, values: [1, Number.NaN] },
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(result.ok).toBe(false);
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
expectNumericValueError(result.error, {
|
|
82
|
+
field: 'values',
|
|
83
|
+
received: Number.NaN,
|
|
84
|
+
index: 1,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
expect(world.get(entity, Numeric).ok).toBe(false);
|
|
88
|
+
expect(world.getStructureEpoch()).toBe(beforeEpoch);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('rejects set array NaN and preserves the complete old value', () => {
|
|
92
|
+
const world = new World();
|
|
93
|
+
const entity = world
|
|
94
|
+
.spawn({ component: Numeric, data: { scalar: 4, values: [1, 2] } })
|
|
95
|
+
.unwrap();
|
|
96
|
+
|
|
97
|
+
const result = world.set(entity, Numeric, { values: [7, Number.NaN] });
|
|
98
|
+
|
|
99
|
+
expect(result.ok).toBe(false);
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
expectNumericValueError(result.error, {
|
|
102
|
+
field: 'values',
|
|
103
|
+
received: Number.NaN,
|
|
104
|
+
index: 1,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const current = world.get(entity, Numeric).unwrap();
|
|
108
|
+
expect(current.scalar).toBe(4);
|
|
109
|
+
expect(Array.from(current.values)).toEqual([1, 2]);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('rejects QueryRow.mut scalar NaN and preserves the old field', () => {
|
|
113
|
+
const world = new World();
|
|
114
|
+
const entity = world
|
|
115
|
+
.spawn({ component: Numeric, data: { scalar: 5, values: [1, 2] } })
|
|
116
|
+
.unwrap();
|
|
117
|
+
const row = world
|
|
118
|
+
.query({ write: [Numeric] })
|
|
119
|
+
.unwrap()
|
|
120
|
+
.at(entity);
|
|
121
|
+
expect(row).toBeDefined();
|
|
122
|
+
let thrown: unknown;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
if (row !== undefined) row.mut(Numeric).scalar = Number.NaN;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
thrown = error;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
expectNumericValueError(thrown, { field: 'scalar', received: Number.NaN });
|
|
131
|
+
expect(world.get(entity, Numeric).unwrap().scalar).toBe(5);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('rejects a deferred command before adding the component', () => {
|
|
135
|
+
const world = new World();
|
|
136
|
+
const entity = world.spawn({ component: Marker, data: {} }).unwrap();
|
|
137
|
+
world
|
|
138
|
+
.addSystem(Update, {
|
|
139
|
+
name: 'nan-preflight-command',
|
|
140
|
+
queries: [],
|
|
141
|
+
fn: (_world, _queries, commands) => {
|
|
142
|
+
commands.addComponent(entity, {
|
|
143
|
+
component: Numeric,
|
|
144
|
+
data: { scalar: Number.NaN, values: [1, 2] },
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
.unwrap();
|
|
149
|
+
|
|
150
|
+
const result = world.update(0);
|
|
151
|
+
|
|
152
|
+
expect(result.ok).toBe(false);
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
expect(result.error.code).toBe('command-failed');
|
|
155
|
+
if (result.error.code === 'command-failed') {
|
|
156
|
+
expectNumericValueError(result.error.detail.cause, {
|
|
157
|
+
field: 'scalar',
|
|
158
|
+
received: Number.NaN,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
expect(world.get(entity, Numeric).ok).toBe(false);
|
|
163
|
+
expect(world.execution.health).toBe('healthy');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('accepts positive Infinity while leaving QuerySpan as the raw numeric boundary', () => {
|
|
167
|
+
const world = new World({ storage: 'shared' });
|
|
168
|
+
const entity = world
|
|
169
|
+
.spawn({
|
|
170
|
+
component: InlineNumeric,
|
|
171
|
+
data: { scalar: Number.POSITIVE_INFINITY, values: [Number.POSITIVE_INFINITY, 2] },
|
|
172
|
+
})
|
|
173
|
+
.unwrap();
|
|
174
|
+
expect(world.set(entity, InlineNumeric, { scalar: Number.POSITIVE_INFINITY }).ok).toBe(true);
|
|
175
|
+
expect(world.get(entity, InlineNumeric).unwrap().scalar).toBe(Number.POSITIVE_INFINITY);
|
|
176
|
+
|
|
177
|
+
const spans = world
|
|
178
|
+
.query({ write: [InlineNumeric] })
|
|
179
|
+
.unwrap()
|
|
180
|
+
.spans()
|
|
181
|
+
.unwrap();
|
|
182
|
+
for (const span of spans) span.mut(InlineNumeric).scalar[0] = Number.NaN;
|
|
183
|
+
|
|
184
|
+
expect(world.get(entity, InlineNumeric).unwrap().scalar).toBeNaN();
|
|
185
|
+
});
|
|
186
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Compile-time + runtime test for Component field-schema literal preservation +
|
|
2
|
+
// 4-probe quintet over SchemaVocabKeyword (w3, AC-09).
|
|
3
|
+
//
|
|
4
|
+
// Locks two invariants:
|
|
5
|
+
//
|
|
6
|
+
// 1. Literal preservation: defineComponent('Foo', { value: 'string' } as
|
|
7
|
+
// const) yields `SchemaOf<typeof Foo>['value']` typed as the literal `'string'`,
|
|
8
|
+
// not widened to plain `string`. AI users that read
|
|
9
|
+
// `SchemaOf<typeof Component>` gets the schema keyword as a literal
|
|
10
|
+
// type --- the basis of `isManagedField` switch / cascade discrimination.
|
|
11
|
+
//
|
|
12
|
+
// 2. 4-probe quintet (w4, plan-strategy §2.2 / D-R3 predicate merge):
|
|
13
|
+
// The legacy string-only + ref-only probes collapse into a single
|
|
14
|
+
// `isManagedField` arm covering both `'string'` and `'unique<T>'`. The
|
|
15
|
+
// remaining probes target the other SchemaVocabKeyword tiers:
|
|
16
|
+
// isManagedField => 'string' | 'unique<T>' (NEW unified arm)
|
|
17
|
+
// isManagedBufferField => 'buffer' / 'buffer<N>'
|
|
18
|
+
// isEntityField => 'entity'
|
|
19
|
+
// isManagedArrayField => 'array<T,N>' / 'array<T>'
|
|
20
|
+
// AI users grep `isXxxField` once and get the discrimination toolkit.
|
|
21
|
+
|
|
22
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
23
|
+
import {
|
|
24
|
+
defineComponent,
|
|
25
|
+
isEntityField,
|
|
26
|
+
isManagedArrayField,
|
|
27
|
+
isManagedBufferField,
|
|
28
|
+
isManagedField,
|
|
29
|
+
type SchemaOf,
|
|
30
|
+
type SchemaVocabKeyword,
|
|
31
|
+
} from '../component';
|
|
32
|
+
|
|
33
|
+
describe('component schema --- literal preservation (w3, AC-09)', () => {
|
|
34
|
+
it("defineComponent('Foo', { value: 'string' }) preserves 'string' literal", () => {
|
|
35
|
+
const Foo = defineComponent('Foo', { value: 'string' });
|
|
36
|
+
type ValueField = SchemaOf<typeof Foo>['value'];
|
|
37
|
+
expectTypeOf<ValueField>().toEqualTypeOf<'string'>();
|
|
38
|
+
|
|
39
|
+
// The token intentionally exposes only name/fields/storage. These
|
|
40
|
+
// negative probes keep removed convenience facts out of autocomplete.
|
|
41
|
+
// @ts-expect-error Component.schema was removed; use SchemaOf<typeof Foo>.
|
|
42
|
+
void Foo.schema;
|
|
43
|
+
// @ts-expect-error Component.id is an ECS-owner fact, not token data.
|
|
44
|
+
void Foo.id;
|
|
45
|
+
// @ts-expect-error Component.defaults lives in componentDefinition(Foo).
|
|
46
|
+
void Foo.defaults;
|
|
47
|
+
// @ts-expect-error Component.toSchemaJSON was removed with the duplicate projection.
|
|
48
|
+
void Foo.toSchemaJSON;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('schema literal is *not* widened to plain `string`', () => {
|
|
52
|
+
const Foo = defineComponent('Foo', { value: 'string' });
|
|
53
|
+
type ValueField = SchemaOf<typeof Foo>['value'];
|
|
54
|
+
// The literal must NOT widen to base `string` --- AI users rely on the
|
|
55
|
+
// narrow type for switch / cascade discrimination.
|
|
56
|
+
expectTypeOf<ValueField>().not.toEqualTypeOf<string>();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('multi-field schemas preserve every keyword literal', () => {
|
|
60
|
+
const Bar = defineComponent('Bar', {
|
|
61
|
+
name: 'string',
|
|
62
|
+
mass: 'f32',
|
|
63
|
+
target: 'entity',
|
|
64
|
+
});
|
|
65
|
+
expectTypeOf<SchemaOf<typeof Bar>['name']>().toEqualTypeOf<'string'>();
|
|
66
|
+
expectTypeOf<SchemaOf<typeof Bar>['mass']>().toEqualTypeOf<'f32'>();
|
|
67
|
+
expectTypeOf<SchemaOf<typeof Bar>['target']>().toEqualTypeOf<'entity'>();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('component schema --- 4-probe quintet (w3, AC-09)', () => {
|
|
72
|
+
it('all four probes have signature `(s: string) => boolean`', () => {
|
|
73
|
+
expectTypeOf(isManagedField).toEqualTypeOf<(s: string) => boolean>();
|
|
74
|
+
expectTypeOf(isManagedBufferField).toEqualTypeOf<(s: string) => boolean>();
|
|
75
|
+
expectTypeOf(isEntityField).toEqualTypeOf<(s: string) => boolean>();
|
|
76
|
+
expectTypeOf(isManagedArrayField).toEqualTypeOf<(s: string) => boolean>();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("isManagedField recognises 'string' literal and 'unique<T>' template", () => {
|
|
80
|
+
// Behavioural assertions (AC-09) --- the unified arm must cover both
|
|
81
|
+
// `'string'` and `'unique<T>'` keywords in a single predicate.
|
|
82
|
+
expectTypeOf(isManagedField('string')).toEqualTypeOf<boolean>();
|
|
83
|
+
expectTypeOf(isManagedField('unique<MeshAsset>')).toEqualTypeOf<boolean>();
|
|
84
|
+
// Runtime cases:
|
|
85
|
+
if (!isManagedField('string')) throw new Error("isManagedField('string') must be true");
|
|
86
|
+
if (!isManagedField('unique<MeshAsset>'))
|
|
87
|
+
throw new Error("isManagedField('unique<MeshAsset>') must be true");
|
|
88
|
+
if (isManagedField('entity')) throw new Error("isManagedField('entity') must be false");
|
|
89
|
+
if (isManagedField('buffer<128>'))
|
|
90
|
+
throw new Error("isManagedField('buffer<128>') must be false");
|
|
91
|
+
// 'buffer' bare keyword is not a SchemaVocabKeyword but the predicate
|
|
92
|
+
// must still reject it (defensive for the erased runtime call site).
|
|
93
|
+
if (isManagedField('buffer')) throw new Error("isManagedField('buffer') must be false");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('SchemaVocabKeyword closed union has 7 members covered by 4 probes', () => {
|
|
97
|
+
// Mapping (membership <-> probe):
|
|
98
|
+
// buffer / buffer<N> => isManagedBufferField
|
|
99
|
+
// ref<T> => isManagedField (NEW unified arm w/ 'string')
|
|
100
|
+
// handle<T> => (no managed probe; ECS treats handles as opaque)
|
|
101
|
+
// entity => isEntityField
|
|
102
|
+
// array<T,N> + array<T> => isManagedArrayField (counts as one)
|
|
103
|
+
// string => isManagedField (NEW unified arm w/ 'unique<T>')
|
|
104
|
+
//
|
|
105
|
+
// The closed union still has exactly 7 template-literal arms; we lock
|
|
106
|
+
// the union shape so any drift (e.g. removing one arm or adding a
|
|
107
|
+
// sixth probe family) shows up as a type error.
|
|
108
|
+
type Sample =
|
|
109
|
+
| 'buffer<128>'
|
|
110
|
+
| 'unique<MaterialAsset>'
|
|
111
|
+
| 'shared<MeshAsset>'
|
|
112
|
+
| 'entity'
|
|
113
|
+
| 'array<f32, 16>'
|
|
114
|
+
| 'array<u32>'
|
|
115
|
+
| 'string';
|
|
116
|
+
// Every member of `Sample` is assignable to `SchemaVocabKeyword`.
|
|
117
|
+
expectTypeOf<Sample>().toMatchTypeOf<SchemaVocabKeyword>();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import { assertComponentStorage, componentDefinition, deepFreeze } from '../component-schema';
|
|
4
|
+
|
|
5
|
+
describe('component schema contract', () => {
|
|
6
|
+
it('deep freezes nested field facts and owner defaults', () => {
|
|
7
|
+
const component = defineComponent('SchemaFreeze', {
|
|
8
|
+
mode: { type: 'enum', default: 1, labels: { idle: 0, run: 1 } },
|
|
9
|
+
payload: { type: 'array<f32, 2>', default: [1, 2] as never },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
expect(Object.isFrozen(component.fields)).toBe(true);
|
|
13
|
+
expect(Object.isFrozen(component.fields.mode)).toBe(true);
|
|
14
|
+
expect(Object.isFrozen(component.fields.mode?.labels)).toBe(true);
|
|
15
|
+
const defaults = componentDefinition(component).defaults;
|
|
16
|
+
expect(Object.isFrozen(defaults)).toBe(true);
|
|
17
|
+
expect(Object.isFrozen(defaults?.payload)).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('keeps the public token surface to name, fields, and storage', () => {
|
|
21
|
+
const component = defineComponent('ComponentSurface', { value: 'f32' });
|
|
22
|
+
expect(Object.keys(component)).toEqual(['name', 'fields', 'storage']);
|
|
23
|
+
expect(Reflect.ownKeys(component)).toEqual(['name', 'fields', 'storage']);
|
|
24
|
+
expect(Object.getPrototypeOf(component)).toBe(Object.prototype);
|
|
25
|
+
expect('schema' in component).toBe(false);
|
|
26
|
+
expect('id' in component).toBe(false);
|
|
27
|
+
expect('defaults' in component).toBe(false);
|
|
28
|
+
expect('toSchemaJSON' in component).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('accepts only the closed table/sparse storage vocabulary', () => {
|
|
32
|
+
expect(() => assertComponentStorage('table')).not.toThrow();
|
|
33
|
+
expect(() => assertComponentStorage('sparse')).not.toThrow();
|
|
34
|
+
expect(() => assertComponentStorage('columnar')).toThrow(/Expected 'table' or 'sparse'/);
|
|
35
|
+
expect(deepFreeze({ nested: { value: 1 } }).nested).toEqual({ value: 1 });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('treats typed arrays and ArrayBuffer as immutable leaf projections', () => {
|
|
39
|
+
const values = new Float32Array([1, 2]);
|
|
40
|
+
const buffer = new ArrayBuffer(8);
|
|
41
|
+
|
|
42
|
+
const projected = deepFreeze({ values, buffer });
|
|
43
|
+
expect(Object.isFrozen(projected)).toBe(true);
|
|
44
|
+
expect(Object.isFrozen(values)).toBe(false);
|
|
45
|
+
expect(Object.isFrozen(buffer)).toBe(false);
|
|
46
|
+
expect(values[0]).toBe(1);
|
|
47
|
+
expect(buffer.byteLength).toBe(8);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects an invalid runtime storage value before registration', () => {
|
|
51
|
+
expect(() => defineComponent('InvalidStorage', {}, { storage: 'columnar' as 'table' })).toThrow(
|
|
52
|
+
/Expected 'table' or 'sparse'/,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Compile-time type-derivation tests for the DefineComponentOptions surface
|
|
2
|
+
// after the M1 buffer/array vocab collapse (w3, AC-05).
|
|
3
|
+
//
|
|
4
|
+
// Locks the `arrayStride` option-bit removal: passing an `arrayStride` map
|
|
5
|
+
// to `defineComponent`'s third options arg is a TS compile-time error after
|
|
6
|
+
// w5. The stride contract migrates to RenderSystem-extract entry-site
|
|
7
|
+
// defensive plus AI user set-site responsibility (plan-strategy §2.3).
|
|
8
|
+
//
|
|
9
|
+
// Pre-M1 (before w5): `arrayStride: { ... }` is a valid option on
|
|
10
|
+
// `DefineComponentOptions` and the @ts-expect-error directive is unsatisfied
|
|
11
|
+
// (TDD red state).
|
|
12
|
+
//
|
|
13
|
+
// Post-M1 (after w5): the option is removed; the directive is satisfied; the
|
|
14
|
+
// surface is narrower.
|
|
15
|
+
|
|
16
|
+
import { describe, it } from 'vitest';
|
|
17
|
+
import { type DefineComponentOptions, defineComponent } from '../component';
|
|
18
|
+
|
|
19
|
+
describe('DefineComponentOptions — arrayStride option-bit removed (w3, AC-05)', () => {
|
|
20
|
+
it('arrayStride is no longer a key on DefineComponentOptions', () => {
|
|
21
|
+
type Opts = DefineComponentOptions;
|
|
22
|
+
// @ts-expect-error 'arrayStride' is no longer a key on DefineComponentOptions
|
|
23
|
+
// after feat-20260515 w5 (stride responsibility migrated to RenderSystem
|
|
24
|
+
// extract entry).
|
|
25
|
+
const opts: Opts = { arrayStride: { f: 16 } };
|
|
26
|
+
void opts;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('defineComponent rejects arrayStride at the call-site', () => {
|
|
30
|
+
// @ts-expect-error 'arrayStride' is no longer accepted by defineComponent
|
|
31
|
+
// after feat-20260515 w5.
|
|
32
|
+
const Bar = defineComponent('Bar', { f: { type: 'array<f32>' } }, { arrayStride: { f: 16 } });
|
|
33
|
+
void Bar;
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// feat-20260614 M5 / w25 — runtime rejection tests for the two retired
|
|
2
|
+
// schema-vocab keyword families. Both renames preserve brand and storage
|
|
3
|
+
// layout; only the keyword + dispatch arm changed. The
|
|
4
|
+
// SchemaUnsupportedFieldError migration hint redirects AI users straight
|
|
5
|
+
// to the new keyword (charter F1 single-entry indexability).
|
|
6
|
+
//
|
|
7
|
+
// 'ref<T>' -> hint contains 'unique<T>' (M1 rename)
|
|
8
|
+
// 'handle<T>' -> hint contains 'shared<T>' (M5 cut)
|
|
9
|
+
//
|
|
10
|
+
// AC-14 (requirements): writing either deprecated literal to defineComponent
|
|
11
|
+
// must throw a structured error with the migration hint pointing at the
|
|
12
|
+
// replacement keyword.
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from 'vitest';
|
|
15
|
+
import { defineComponent } from '../component';
|
|
16
|
+
|
|
17
|
+
describe('w25 deprecated schema-vocab rejection', () => {
|
|
18
|
+
it("'handle<MeshAsset>' throws SchemaUnsupportedFieldError + hint mentions 'shared<MeshAsset>'", () => {
|
|
19
|
+
// gate-allow:ecs-brand
|
|
20
|
+
let caught: unknown;
|
|
21
|
+
try {
|
|
22
|
+
defineComponent('w25HandleSample', { f: { type: 'handle<MeshAsset>' } } as never); // gate-allow:ecs-brand
|
|
23
|
+
} catch (e) {
|
|
24
|
+
caught = e;
|
|
25
|
+
}
|
|
26
|
+
expect(caught).toBeDefined();
|
|
27
|
+
const e = caught as { code: string; hint: string };
|
|
28
|
+
expect(e.code).toBe('schema-unsupported-field');
|
|
29
|
+
expect(e.hint).toContain("'shared<MeshAsset>'");
|
|
30
|
+
expect(e.hint).toContain('feat-20260614 M5');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("'ref<PhysicsBody>' throws SchemaUnsupportedFieldError + hint mentions 'unique<PhysicsBody>'", () => {
|
|
34
|
+
// gate-allow:ecs-brand
|
|
35
|
+
let caught: unknown;
|
|
36
|
+
try {
|
|
37
|
+
defineComponent('w25RefSample', { f: { type: 'ref<PhysicsBody>' } } as never); // gate-allow:ecs-brand
|
|
38
|
+
} catch (e) {
|
|
39
|
+
caught = e;
|
|
40
|
+
}
|
|
41
|
+
expect(caught).toBeDefined();
|
|
42
|
+
const e = caught as { code: string; hint: string };
|
|
43
|
+
expect(e.code).toBe('schema-unsupported-field');
|
|
44
|
+
expect(e.hint).toContain("'unique<PhysicsBody>'");
|
|
45
|
+
expect(e.hint).toContain('feat-20260614 M1');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("'shared<MeshAsset>' is accepted (the new keyword still works)", () => {
|
|
49
|
+
expect(() => {
|
|
50
|
+
defineComponent('w25SharedSample', { f: { type: 'shared<MeshAsset>' } });
|
|
51
|
+
}).not.toThrow();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("'unique<PhysicsBody>' is accepted (the new keyword still works)", () => {
|
|
55
|
+
expect(() => {
|
|
56
|
+
defineComponent('w25UniqueSample', { f: { type: 'unique<PhysicsBody>' } });
|
|
57
|
+
}).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { defineComponent, World } from '../index';
|
|
4
|
+
|
|
5
|
+
describe('World.despawnAll', () => {
|
|
6
|
+
it('routes every live entity through normal despawn cleanup', () => {
|
|
7
|
+
const Marker = defineComponent('DespawnAllMarker', { value: 'u32' });
|
|
8
|
+
const world = new World();
|
|
9
|
+
world.spawn({ component: Marker, data: { value: 1 } }).unwrap();
|
|
10
|
+
world.spawn({ component: Marker, data: { value: 2 } }).unwrap();
|
|
11
|
+
|
|
12
|
+
const result = world.despawnAll();
|
|
13
|
+
|
|
14
|
+
expect(result.ok).toBe(true);
|
|
15
|
+
expect(world.inspect().entityCount).toBe(0);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { defineComponent, defineRelationship, Update, World } from '@forgeax/engine-ecs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const { source: ChildOf, target: Children } = defineRelationship({
|
|
5
|
+
sourceName: 'ReductionChildOf',
|
|
6
|
+
sourceField: 'parent',
|
|
7
|
+
targetName: 'ReductionChildren',
|
|
8
|
+
targetField: 'entities',
|
|
9
|
+
});
|
|
10
|
+
const { source: AnimatedBy, target: AnimationTargets } = defineRelationship({
|
|
11
|
+
sourceName: 'ReductionAnimatedBy',
|
|
12
|
+
sourceField: 'player',
|
|
13
|
+
targetName: 'ReductionAnimationTargets',
|
|
14
|
+
targetField: 'targets',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('M0 ECS reduction characterization', () => {
|
|
18
|
+
it('F-01: exposes pending reservations after a system throws', () => {
|
|
19
|
+
const Marker = defineComponent('M0PendingMarker', {});
|
|
20
|
+
const world = new World();
|
|
21
|
+
|
|
22
|
+
world.addSystem(Update, {
|
|
23
|
+
name: 'throw-after-reservation',
|
|
24
|
+
queries: [],
|
|
25
|
+
fn: (_world, _queries, commands) => {
|
|
26
|
+
commands.spawn({ component: Marker, data: {} });
|
|
27
|
+
throw new Error('M0 system failure');
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = world.update(0);
|
|
32
|
+
expect(result.ok).toBe(false);
|
|
33
|
+
if (!result.ok) {
|
|
34
|
+
expect(result.error.code).toBe('system-failed');
|
|
35
|
+
if (result.error.code === 'system-failed') {
|
|
36
|
+
expect(result.error.detail.cause).toBeInstanceOf(Error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
expect(world.execution.health).toBe('poisoned');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('F-02: preserves a failed command result at the public update boundary', () => {
|
|
43
|
+
const Marker = defineComponent('M0CommandMarker', {});
|
|
44
|
+
const world = new World();
|
|
45
|
+
const entity = world.spawn({ component: Marker, data: {} }).unwrap();
|
|
46
|
+
const before = world.inspect().entityCount;
|
|
47
|
+
|
|
48
|
+
world.addSystem(Update, {
|
|
49
|
+
name: 'duplicate-component-command',
|
|
50
|
+
queries: [],
|
|
51
|
+
fn: (_world, _queries, commands) => {
|
|
52
|
+
commands.addComponent(entity, { component: Marker, data: {} });
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const result = world.update(0);
|
|
57
|
+
expect(result.ok).toBe(false);
|
|
58
|
+
if (!result.ok) {
|
|
59
|
+
expect(result.error.code).toBe('command-failed');
|
|
60
|
+
if (result.error.code === 'command-failed') {
|
|
61
|
+
expect(result.error.detail.systemName).toBe('duplicate-component-command');
|
|
62
|
+
expect(result.error.detail.commandKind).toBe('addComponent');
|
|
63
|
+
expect((result.error.detail.cause as { readonly code?: string }).code).toBe(
|
|
64
|
+
'component-already-present',
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
expect(world.execution.health).toBe('healthy');
|
|
69
|
+
expect(world.inspect().entityCount).toBe(before);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('F-03: despawn does not own shared payload disposal', () => {
|
|
73
|
+
const world = new World();
|
|
74
|
+
const payload = { id: 1 };
|
|
75
|
+
const handle = world.allocSharedRef('M0Asset', payload);
|
|
76
|
+
const Holder = defineComponent('M0SharedHolder', { asset: 'shared<M0Asset>' });
|
|
77
|
+
const entity = world.spawn({ component: Holder, data: { asset: handle } }).unwrap();
|
|
78
|
+
world.sharedRefs.release(handle).unwrap();
|
|
79
|
+
|
|
80
|
+
expect(() => world.despawn(entity)).not.toThrow();
|
|
81
|
+
expect(world.sharedRefs.readReleaseEvidence()).toEqual([
|
|
82
|
+
{ payload, refcount: 0, generation: 1, evidence: 'released' },
|
|
83
|
+
]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('F-07: rejects undefined shared payloads before allocating a slot', () => {
|
|
87
|
+
const world = new World();
|
|
88
|
+
expect(() => world.allocSharedRef('M0UndefinedAsset', undefined)).toThrow();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('T-02: rejects an invalid managed-array payload without a row delta', () => {
|
|
92
|
+
const Values = defineComponent('M0ManagedValues', { values: 'array<f32>' });
|
|
93
|
+
const world = new World();
|
|
94
|
+
const beforeEntityCount = world.inspect().entityCount;
|
|
95
|
+
const beforeEpoch = world.getStructureEpoch();
|
|
96
|
+
|
|
97
|
+
const result = world.spawn({
|
|
98
|
+
component: Values,
|
|
99
|
+
data: { values: { invalid: true } as never },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(result.ok).toBe(false);
|
|
103
|
+
expect(world.inspect().entityCount).toBe(beforeEntityCount);
|
|
104
|
+
expect(world.getStructureEpoch()).toBe(beforeEpoch);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('keeps ChildOf/Children and AnimatedBy/AnimationTargets as materialized target pairs', () => {
|
|
108
|
+
const world = new World();
|
|
109
|
+
const parent = world.spawn({ component: Children, data: { entities: [] } }).unwrap();
|
|
110
|
+
const child = world.spawn({ component: ChildOf, data: { parent } }).unwrap();
|
|
111
|
+
const children = world.get(parent, Children).unwrap().entities;
|
|
112
|
+
expect([...children]).toContain(child);
|
|
113
|
+
|
|
114
|
+
const player = world.spawn({ component: AnimationTargets, data: { targets: [] } }).unwrap();
|
|
115
|
+
const target = world.spawn({ component: AnimatedBy, data: { player } }).unwrap();
|
|
116
|
+
const targets = world.get(player, AnimationTargets).unwrap().targets;
|
|
117
|
+
expect([...targets]).toContain(target);
|
|
118
|
+
|
|
119
|
+
const childrenEpoch = world.getStructureEpoch();
|
|
120
|
+
// Relationship target components are intentionally not writable through
|
|
121
|
+
// the typed mutation facade; the runtime returns a structured refusal.
|
|
122
|
+
// @ts-expect-error relationship target is a read-only projection.
|
|
123
|
+
const targetWrite = world.set(parent, Children, { entities: [] });
|
|
124
|
+
// @ts-expect-error relationship target is a read-only projection.
|
|
125
|
+
const animationTargetWrite = world.set(player, AnimationTargets, { targets: [] });
|
|
126
|
+
expect(childrenEpoch).toBeLessThanOrEqual(world.getStructureEpoch());
|
|
127
|
+
expect(targetWrite.ok).toBe(false);
|
|
128
|
+
expect(animationTargetWrite.ok).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
});
|