@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,325 @@
|
|
|
1
|
+
// m3-projection-kernel-test — ECS externalization projection kernel semantic tests.
|
|
2
|
+
//
|
|
3
|
+
// Validates pure-kernel behaviours: owned snapshot production, default fill,
|
|
4
|
+
// component/field transient exclusion, portable scalar/array values, injectable
|
|
5
|
+
// entity and fixed/variable entity array remap. Uses reflection arrayMeta rather
|
|
6
|
+
// than schema-text classifiers.
|
|
7
|
+
//
|
|
8
|
+
// TDD red phase: these tests are expected to FAIL until the kernel implementation
|
|
9
|
+
// (m3-externalization-kernel-impl) is complete.
|
|
10
|
+
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { type Component, defineComponent } from '../component';
|
|
13
|
+
import {
|
|
14
|
+
classifyEntityField,
|
|
15
|
+
createEntityRemap,
|
|
16
|
+
projectComponentData,
|
|
17
|
+
remapEntityFieldValue,
|
|
18
|
+
} from '../externalization/index';
|
|
19
|
+
import { World } from '../world';
|
|
20
|
+
|
|
21
|
+
// ── Test components ──────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const Pos = defineComponent('TestPos', {
|
|
24
|
+
x: { type: 'f32', default: 0 },
|
|
25
|
+
y: { type: 'f32', default: 0 },
|
|
26
|
+
z: { type: 'f32', default: 0 },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const Mixed = defineComponent('TestMixed', {
|
|
30
|
+
health: { type: 'u32', default: 100 },
|
|
31
|
+
name: { type: 'string', default: 'unnamed' },
|
|
32
|
+
active: { type: 'bool', default: true },
|
|
33
|
+
scale: { type: 'f32', default: 1.0 },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const WithEntity = defineComponent('TestWithEntity', {
|
|
37
|
+
target: { type: 'entity' },
|
|
38
|
+
friends: { type: 'array<entity>', default: [] },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const FixedArrayEntity = defineComponent('TestFixedArrayEntity', {
|
|
42
|
+
slots: { type: 'array<entity, 4>', default: [0, 0, 0, 0] },
|
|
43
|
+
// biome-ignore lint/suspicious/noExplicitAny: test component with array<entity,4> default; literal array shape is a branded handle
|
|
44
|
+
} as any) as Component;
|
|
45
|
+
|
|
46
|
+
const VariableArrayEntity = defineComponent('TestVariableArrayEntity', {
|
|
47
|
+
items: { type: 'array<entity>', default: [] },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const TransientComp = defineComponent(
|
|
51
|
+
'TestTransientComp',
|
|
52
|
+
{
|
|
53
|
+
x: { type: 'f32', default: 0 },
|
|
54
|
+
},
|
|
55
|
+
{ transient: true },
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const FieldTransient = defineComponent('TestFieldTransient', {
|
|
59
|
+
keep: { type: 'f32', default: 0 },
|
|
60
|
+
derived: { type: 'f32', default: 0, transient: true },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const EnumComp = defineComponent('TestEnumComp', {
|
|
64
|
+
kind: { type: 'enum', default: 0, labels: { static: 0, dynamic: 1 } },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const BufferComp = defineComponent('TestBufferComp', {
|
|
68
|
+
data: { type: 'buffer', default: 0 },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const FixedBufferComp = defineComponent('TestFixedBufferComp', {
|
|
72
|
+
data: { type: 'buffer<256>', default: 0 },
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const FixedArrayComp = defineComponent('TestFixedArrayComp', {
|
|
76
|
+
coords: {
|
|
77
|
+
type: 'array<f32, 3>', // biome-ignore lint/suspicious/noExplicitAny: test component with array<f32,3> default; literal array shape is a branded handle
|
|
78
|
+
default: [0, 0, 0] as any,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const EnumNoLabels = defineComponent('TestEnumNoLabels', {
|
|
83
|
+
kind: { type: 'enum', default: 0 },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function fieldsOf(r: Record<string, unknown>): string[] {
|
|
89
|
+
return Object.keys(r).sort();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function hasField(r: Record<string, unknown>, field: string): boolean {
|
|
93
|
+
return field in r;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
describe('m3 — projection kernel: owned snapshot and default fill', () => {
|
|
99
|
+
it('(a) produces a record with all schema fields filled', () => {
|
|
100
|
+
const result = projectComponentData(Pos as Component, {});
|
|
101
|
+
expect(fieldsOf(result)).toEqual(['x', 'y', 'z']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('(b) layer-1 explicit values survive projection', () => {
|
|
105
|
+
const result = projectComponentData(Pos as Component, { x: 42, y: 7, z: 99 });
|
|
106
|
+
expect(result.x).toBe(42);
|
|
107
|
+
expect(result.y).toBe(7);
|
|
108
|
+
expect(result.z).toBe(99);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('(c) layer-2 defaults fill missing fields', () => {
|
|
112
|
+
// Mixed.health has layer-2 default 100
|
|
113
|
+
const result = projectComponentData(Mixed as Component, { name: 'hero' });
|
|
114
|
+
expect(result.health).toBe(100);
|
|
115
|
+
expect(result.name).toBe('hero');
|
|
116
|
+
expect(result.active).toBe(true);
|
|
117
|
+
expect(result.scale).toBe(1.0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('(d) layer-3 type defaults fill completely missing input', () => {
|
|
121
|
+
const result = projectComponentData(Pos as Component, undefined);
|
|
122
|
+
expect(result.x).toBe(0);
|
|
123
|
+
expect(result.y).toBe(0);
|
|
124
|
+
expect(result.z).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('(e) projection produces a new object (owned snapshot)', () => {
|
|
128
|
+
const input = { x: 1, y: 2, z: 3 };
|
|
129
|
+
const result = projectComponentData(Pos as Component, input);
|
|
130
|
+
// Mutating the result should not affect the input
|
|
131
|
+
(result as Record<string, number>).x = 999;
|
|
132
|
+
expect(input.x).toBe(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('(f) string field survives projection', () => {
|
|
136
|
+
const result = projectComponentData(Mixed as Component, { name: 'warrior' });
|
|
137
|
+
expect(result.name).toBe('warrior');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('(g) bool field survives projection', () => {
|
|
141
|
+
const result = projectComponentData(Mixed as Component, { active: false });
|
|
142
|
+
expect(result.active).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('m3 — projection kernel: component/field transient exclusion', () => {
|
|
147
|
+
it('(a) component-level transient excludes all fields from projection', () => {
|
|
148
|
+
// TransientComp is transient: true — should be detected as fully transient
|
|
149
|
+
const result = projectComponentData(TransientComp as Component, { x: 5 });
|
|
150
|
+
// The stub currently returns all fields — this will FAIL until kernel is implemented
|
|
151
|
+
expect(fieldsOf(result)).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('(b) field-level transient excludes only that field', () => {
|
|
155
|
+
const result = projectComponentData(FieldTransient as Component, { keep: 10, derived: 20 });
|
|
156
|
+
expect(hasField(result, 'keep')).toBe(true);
|
|
157
|
+
// `derived` is transient — should NOT be in the projection
|
|
158
|
+
expect(hasField(result, 'derived')).toBe(false);
|
|
159
|
+
expect(result.keep).toBe(10);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('(c) non-transient component with no transient fields projects all fields', () => {
|
|
163
|
+
const result = projectComponentData(Pos as Component, { x: 1, y: 2, z: 3 });
|
|
164
|
+
expect(fieldsOf(result)).toEqual(['x', 'y', 'z']);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('m3 — projection kernel: portable scalar/array values', () => {
|
|
169
|
+
it('(a) fixed array<f32,3> field survives projection', () => {
|
|
170
|
+
const result = projectComponentData(FixedArrayComp as Component, { coords: [1, 2, 3] });
|
|
171
|
+
expect(Array.isArray(result.coords)).toBe(true);
|
|
172
|
+
expect((result.coords as number[]).length).toBe(3);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('(b) enum field with labels survives projection', () => {
|
|
176
|
+
const result = projectComponentData(EnumComp as Component, { kind: 1 });
|
|
177
|
+
expect(result.kind).toBe(1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('(c) enum field without labels survives projection', () => {
|
|
181
|
+
const result = projectComponentData(EnumNoLabels as Component, { kind: 0 });
|
|
182
|
+
expect(result.kind).toBe(0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('(d) buffer field survives projection', () => {
|
|
186
|
+
const result = projectComponentData(BufferComp as Component, {});
|
|
187
|
+
expect(hasField(result, 'data')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('(e) fixed buffer<256> field survives projection', () => {
|
|
191
|
+
const result = projectComponentData(FixedBufferComp as Component, {});
|
|
192
|
+
expect(hasField(result, 'data')).toBe(true);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('m3 — projection kernel: injectable entity remap', () => {
|
|
197
|
+
it('(a) scalar entity field is remapped through injectable function', () => {
|
|
198
|
+
const remap = (e: number) => e + 1000;
|
|
199
|
+
const result = projectComponentData(WithEntity as Component, { target: 42 }, remap);
|
|
200
|
+
expect(result.target).toBe(1042);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('(b) variable array<entity> field elements are remapped', () => {
|
|
204
|
+
const remap = (e: number) => e + 1000;
|
|
205
|
+
const result = projectComponentData(WithEntity as Component, { friends: [1, 2, 3] }, remap);
|
|
206
|
+
const friends = result.friends as number[];
|
|
207
|
+
expect(friends).toEqual([1001, 1002, 1003]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('(c) fixed array<entity,4> field elements are remapped', () => {
|
|
211
|
+
const remap = (e: number) => e + 1000;
|
|
212
|
+
const result = projectComponentData(
|
|
213
|
+
FixedArrayEntity as Component,
|
|
214
|
+
{ slots: [5, 6, 7, 8] },
|
|
215
|
+
remap,
|
|
216
|
+
);
|
|
217
|
+
const slots = result.slots as number[];
|
|
218
|
+
expect(slots).toEqual([1005, 1006, 1007, 1008]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('(d) no remap function leaves entity values unchanged', () => {
|
|
222
|
+
const result = projectComponentData(WithEntity as Component, { target: 42, friends: [1, 2] });
|
|
223
|
+
expect(result.target).toBe(42);
|
|
224
|
+
expect(result.friends).toEqual([1, 2]);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('(e) entity field with ENTITY_NULL_RAW is remapped if in range', () => {
|
|
228
|
+
const remap = (e: number) => (e === 0xffffffff ? e : e + 1000);
|
|
229
|
+
const result = projectComponentData(WithEntity as Component, { target: 0xffffffff }, remap);
|
|
230
|
+
expect(result.target).toBe(0xffffffff);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('(f) remaps a typed entity array read from an actual World', () => {
|
|
234
|
+
const world = new World();
|
|
235
|
+
const target = world.spawn().unwrap();
|
|
236
|
+
const holder = world
|
|
237
|
+
.spawn({ component: VariableArrayEntity, data: { items: [target] } as never })
|
|
238
|
+
.unwrap();
|
|
239
|
+
const raw = world.get(holder, VariableArrayEntity).unwrap();
|
|
240
|
+
const result = projectComponentData(
|
|
241
|
+
VariableArrayEntity as Component,
|
|
242
|
+
raw,
|
|
243
|
+
(entity) => entity + 1000,
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(result.items).toEqual([target + 1000]);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('m3 — projection kernel: entity field classification (arrayMeta-driven)', () => {
|
|
251
|
+
it('(a) `entity` type field is classified as scalar entity', () => {
|
|
252
|
+
const kind = classifyEntityField(WithEntity as Component, 'target');
|
|
253
|
+
expect(kind).toEqual({ kind: 'entity', isArray: false });
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it('(b) `array<entity>` type field is classified as array entity', () => {
|
|
257
|
+
const kind = classifyEntityField(WithEntity as Component, 'friends');
|
|
258
|
+
expect(kind).toEqual({ kind: 'entity', isArray: true });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('(c) `array<entity,4>` type field is classified as array entity', () => {
|
|
262
|
+
const kind = classifyEntityField(FixedArrayEntity as Component, 'slots');
|
|
263
|
+
expect(kind).toEqual({ kind: 'entity', isArray: true });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('(d) non-entity field returns null classification', () => {
|
|
267
|
+
const kind = classifyEntityField(Pos as Component, 'x');
|
|
268
|
+
expect(kind).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('(e) classification uses arrayMeta.length presence, not schema text parsing', () => {
|
|
272
|
+
// `array<entity,4>` → arrayMeta has length=4; `array<entity>` → arrayMeta has no length
|
|
273
|
+
// Both classify as isArray: true regardless of fixed/variable
|
|
274
|
+
const fixedKind = classifyEntityField(FixedArrayEntity as Component, 'slots');
|
|
275
|
+
const variableKind = classifyEntityField(VariableArrayEntity as Component, 'items');
|
|
276
|
+
expect(fixedKind?.isArray).toBe(true);
|
|
277
|
+
expect(variableKind?.isArray).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe('m3 — remap: createEntityRemap from mapping table', () => {
|
|
282
|
+
it('(a) creates a remap function that translates source ids to target ids', () => {
|
|
283
|
+
const mapping = new Uint32Array([0, 100, 200, 300]);
|
|
284
|
+
const remap = createEntityRemap(mapping);
|
|
285
|
+
expect(remap(0)).toBe(0);
|
|
286
|
+
expect(remap(1)).toBe(100);
|
|
287
|
+
expect(remap(2)).toBe(200);
|
|
288
|
+
expect(remap(3)).toBe(300);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('(b) out-of-range indices return identity', () => {
|
|
292
|
+
const mapping = new Uint32Array([0, 10]);
|
|
293
|
+
const remap = createEntityRemap(mapping);
|
|
294
|
+
expect(remap(5)).toBe(5);
|
|
295
|
+
expect(remap(999)).toBe(999);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('(c) works with plain number arrays', () => {
|
|
299
|
+
const mapping = [0, 10, 20];
|
|
300
|
+
const remap = createEntityRemap(mapping);
|
|
301
|
+
expect(remap(0)).toBe(0);
|
|
302
|
+
expect(remap(1)).toBe(10);
|
|
303
|
+
expect(remap(2)).toBe(20);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('m3 — remap: remapEntityFieldValue', () => {
|
|
308
|
+
it('(a) scalar entity value is remapped', () => {
|
|
309
|
+
const remap = (e: number) => e + 100;
|
|
310
|
+
const result = remapEntityFieldValue(42, { kind: 'entity', isArray: false }, remap);
|
|
311
|
+
expect(result).toBe(142);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('(b) array<entity> values are remapped element-wise', () => {
|
|
315
|
+
const remap = (e: number) => e + 100;
|
|
316
|
+
const result = remapEntityFieldValue([1, 2, 3], { kind: 'entity', isArray: true }, remap);
|
|
317
|
+
expect(result).toEqual([101, 102, 103]);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('(c) null kind passes value through unchanged', () => {
|
|
321
|
+
const remap = (e: number) => e + 100;
|
|
322
|
+
const result = remapEntityFieldValue(42, null, remap);
|
|
323
|
+
expect(result).toBe(42);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { expectTypeOf } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import {
|
|
4
|
+
createRenderReadLease,
|
|
5
|
+
type RenderChangeBatch,
|
|
6
|
+
type RenderProjectionRequest,
|
|
7
|
+
} from '../projection/index';
|
|
8
|
+
import { World } from '../world';
|
|
9
|
+
|
|
10
|
+
const Position = defineComponent('RenderReadTypePosition', { x: 'f32' });
|
|
11
|
+
const world = new World();
|
|
12
|
+
const lease = createRenderReadLease(world);
|
|
13
|
+
const request: RenderProjectionRequest = {
|
|
14
|
+
components: [{ component: Position, fields: ['x'] }],
|
|
15
|
+
};
|
|
16
|
+
const batch = lease.readChanges(lease.inspectCursor());
|
|
17
|
+
const projection = lease.querySpans(request);
|
|
18
|
+
|
|
19
|
+
expectTypeOf(lease.worldIdentity).toEqualTypeOf<string>();
|
|
20
|
+
expectTypeOf(lease.generation).toBeNumber();
|
|
21
|
+
expectTypeOf(batch).toMatchTypeOf<RenderChangeBatch>();
|
|
22
|
+
expectTypeOf(projection.generation).toBeNumber();
|
|
23
|
+
expectTypeOf<NonNullable<(typeof projection.spans)[number]['fields']['x']>>().toMatchTypeOf<
|
|
24
|
+
ArrayLike<number>
|
|
25
|
+
>();
|
|
26
|
+
|
|
27
|
+
// @ts-expect-error RenderReadLease does not expose storage internals.
|
|
28
|
+
lease._getArrayView;
|
|
29
|
+
// @ts-expect-error a renderer projection is not a public lease.
|
|
30
|
+
projection.readChanges;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import { createRenderReadLease, type RenderProjectionRequest } from '../projection/index';
|
|
4
|
+
import { World } from '../world';
|
|
5
|
+
|
|
6
|
+
const Position = defineComponent('RenderReadPosition', {
|
|
7
|
+
x: 'f32',
|
|
8
|
+
y: 'f32',
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const request: RenderProjectionRequest = {
|
|
12
|
+
components: [{ component: Position, fields: ['x', 'y'] }],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
describe('RenderReadLease contract', () => {
|
|
16
|
+
it('attaches, exposes a world generation, and reads continuous spans', () => {
|
|
17
|
+
const world = new World();
|
|
18
|
+
world.spawn({ component: Position, data: { x: 1, y: 2 } }).unwrap();
|
|
19
|
+
const lease = createRenderReadLease(world);
|
|
20
|
+
|
|
21
|
+
expect(lease.worldIdentity).toBe(world.identity);
|
|
22
|
+
expect(lease.generation).toBeGreaterThan(0);
|
|
23
|
+
const projection = lease.querySpans(request);
|
|
24
|
+
expect(projection.generation).toBe(lease.generation);
|
|
25
|
+
expect(projection.spans).toHaveLength(1);
|
|
26
|
+
expect(projection.spans[0]?.fields.x).toEqual(new Float32Array([1]));
|
|
27
|
+
expect(projection.spans[0]?.fields.y).toEqual(new Float32Array([2]));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('publishes world and shared-ref changes through one cursor contract', () => {
|
|
31
|
+
const world = new World();
|
|
32
|
+
const lease = createRenderReadLease(world);
|
|
33
|
+
const cursor = lease.inspectCursor();
|
|
34
|
+
const entity = world.spawn({ component: Position, data: { x: 0, y: 0 } }).unwrap();
|
|
35
|
+
world.set(entity, Position, { x: 3 }).unwrap();
|
|
36
|
+
const batch = lease.readChanges(cursor);
|
|
37
|
+
|
|
38
|
+
expect(batch.status).toBe('ok');
|
|
39
|
+
if (batch.status !== 'ok') return;
|
|
40
|
+
expect(batch.cursor).toBeGreaterThan(cursor);
|
|
41
|
+
expect(batch.world.records.some((record) => record.kind === 'component-changed')).toBe(true);
|
|
42
|
+
expect(batch.sharedRefs.status).toBe('ok');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('reports overflow and requires an explicit resync', () => {
|
|
46
|
+
const world = new World();
|
|
47
|
+
const lease = createRenderReadLease(world);
|
|
48
|
+
const staleCursor = 0;
|
|
49
|
+
|
|
50
|
+
for (let index = 0; index < 65537; index += 1) {
|
|
51
|
+
const entity = world.spawn({ component: Position, data: { x: index, y: 0 } }).unwrap();
|
|
52
|
+
world.despawn(entity).unwrap();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const batch = lease.readChanges(staleCursor);
|
|
56
|
+
expect(batch.status).toBe('overflow');
|
|
57
|
+
if (batch.status !== 'overflow') return;
|
|
58
|
+
expect(batch.resync).toBe(true);
|
|
59
|
+
expect(batch.oldestAvailable).toBeGreaterThan(staleCursor);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('detaches idempotently and rejects reads after dispose', () => {
|
|
63
|
+
const lease = createRenderReadLease(new World());
|
|
64
|
+
lease.dispose();
|
|
65
|
+
lease.dispose();
|
|
66
|
+
|
|
67
|
+
expect(() => lease.inspectCursor()).toThrow(/disposed/i);
|
|
68
|
+
expect(() => lease.querySpans(request)).toThrow(/disposed/i);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('distinguishes leases from renderer-owned projections', () => {
|
|
72
|
+
const world = new World();
|
|
73
|
+
const lease = createRenderReadLease(world);
|
|
74
|
+
const projection = lease.querySpans(request);
|
|
75
|
+
|
|
76
|
+
expect(projection).not.toBe(lease);
|
|
77
|
+
expect(Object.keys(projection)).toEqual(['generation', 'sharedRefEpoch', 'spans']);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const rootSource = readFileSync(new URL('../index.ts', import.meta.url), 'utf8');
|
|
5
|
+
|
|
6
|
+
const redundantRootNames = [
|
|
7
|
+
'createEntityRemap',
|
|
8
|
+
'isComponentFullyTransient',
|
|
9
|
+
'isComponentPortable',
|
|
10
|
+
'isFieldPortable',
|
|
11
|
+
'remapEntityFieldValue',
|
|
12
|
+
] as const;
|
|
13
|
+
|
|
14
|
+
describe('ECS externalization root surface', () => {
|
|
15
|
+
it('keeps the kernel on its dedicated subpath', async () => {
|
|
16
|
+
for (const name of redundantRootNames) {
|
|
17
|
+
expect(rootSource).not.toContain(name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const root = (await import('../index')) as Record<string, unknown>;
|
|
21
|
+
for (const name of redundantRootNames) {
|
|
22
|
+
expect(root).not.toHaveProperty(name);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const externalization = (await import('../externalization/index')) as Record<string, unknown>;
|
|
26
|
+
for (const name of redundantRootNames) {
|
|
27
|
+
expect(externalization).toHaveProperty(name);
|
|
28
|
+
}
|
|
29
|
+
expect(externalization).toHaveProperty('classifyEntityField');
|
|
30
|
+
expect(externalization).toHaveProperty('projectComponentData');
|
|
31
|
+
expect(externalization).toHaveProperty('validateProfileComponents');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// solo round-24 (P7 residue): field-level enum-labels reflection.
|
|
2
|
+
//
|
|
3
|
+
// An `enum` field stores a bare u32 variant index; the label→value map
|
|
4
|
+
// historically lived in a SEPARATE per-package const map (`RigidBodyTypeValue`)
|
|
5
|
+
// with no schema attachment, so no reflection consumer (the editor's
|
|
6
|
+
// describeComponent, an inspector UI) could learn the legal variants + their
|
|
7
|
+
// integers — a docs-only user had to read engine source (a back door, recurring
|
|
8
|
+
// friction #1 in solo rounds 15/20/22).
|
|
9
|
+
//
|
|
10
|
+
// The fix attaches an optional `labels` map to the enum FieldDescriptor,
|
|
11
|
+
// aggregated into `component.fields[field].labels` (FieldReflection), mirroring
|
|
12
|
+
// how `default` / `transient` / `arrayMeta` are already per-field reflection
|
|
13
|
+
// data. This test asserts the reflection round-trips (revert-to-red: drop the
|
|
14
|
+
// `if (desc.labels !== undefined) row.labels = …` aggregation line → red).
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from 'vitest';
|
|
17
|
+
import { type Component, defineComponent } from '../component';
|
|
18
|
+
|
|
19
|
+
function fieldLabels(comp: Component, field: string): Readonly<Record<string, number>> | undefined {
|
|
20
|
+
const reflection = comp.fields[field as keyof typeof comp.fields] as
|
|
21
|
+
| { readonly labels?: Readonly<Record<string, number>> }
|
|
22
|
+
| undefined;
|
|
23
|
+
return reflection?.labels;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('field-level enum-labels reflection (solo round-24)', () => {
|
|
27
|
+
it('(a) an enum field declared with labels reflects the label→value map', () => {
|
|
28
|
+
const C = defineComponent('R24_EnumLabelsA', {
|
|
29
|
+
type: { type: 'enum', default: 1, labels: { static: 0, dynamic: 1, kinematic: 2 } },
|
|
30
|
+
mass: { type: 'f32', default: 1 },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(fieldLabels(C, 'type')).toEqual({ static: 0, dynamic: 1, kinematic: 2 });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('(b) a non-enum / label-less field reflects labels as absent (control)', () => {
|
|
37
|
+
const C = defineComponent('R24_EnumLabelsB', {
|
|
38
|
+
type: { type: 'enum', default: 1, labels: { static: 0, dynamic: 1, kinematic: 2 } },
|
|
39
|
+
mass: { type: 'f32', default: 1 },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// exactOptionalPropertyTypes: undeclared → absence (undefined), not {}.
|
|
43
|
+
expect(fieldLabels(C, 'mass')).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('(c) bare-keyword field form leaves labels absent', () => {
|
|
47
|
+
const C = defineComponent('R24_EnumLabelsC', {
|
|
48
|
+
shape: 'enum',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(fieldLabels(C, 'shape')).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('(d) the reflected labels map is frozen (a stable read-only projection)', () => {
|
|
55
|
+
const source = { static: 0, dynamic: 1, kinematic: 2 };
|
|
56
|
+
const C = defineComponent('R24_EnumLabelsD', {
|
|
57
|
+
type: { type: 'enum', default: 0, labels: source },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const labels = fieldLabels(C, 'type');
|
|
61
|
+
expect(Object.isFrozen(labels)).toBe(true);
|
|
62
|
+
// Copied, not aliased: mutating the source afterward does not leak in.
|
|
63
|
+
(source as Record<string, number>).extra = 9;
|
|
64
|
+
expect(labels).toEqual({ static: 0, dynamic: 1, kinematic: 2 });
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// R0-03A — producer-owned component field-shape coverage.
|
|
2
|
+
//
|
|
3
|
+
// The fixture is deliberately imported from the engine owner. This test proves
|
|
4
|
+
// the registry and FieldReflection carry the same eight shape facts that a
|
|
5
|
+
// downstream Inspector/Gateway consumer will read; it does not maintain a
|
|
6
|
+
// second editor-side component list.
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from 'vitest';
|
|
9
|
+
import {
|
|
10
|
+
FieldShapeContainerFixture,
|
|
11
|
+
FieldShapePrimitiveFixture,
|
|
12
|
+
} from '../__fixtures__/field-shape-fixture';
|
|
13
|
+
import { componentSchema } from '../component';
|
|
14
|
+
import { componentDefinition } from '../component-schema';
|
|
15
|
+
|
|
16
|
+
describe('R0-03A producer-owned field-shape fixtures', () => {
|
|
17
|
+
it('covers every required field shape through component reflection', () => {
|
|
18
|
+
const primitive = FieldShapePrimitiveFixture.fields;
|
|
19
|
+
const container = FieldShapeContainerFixture.fields;
|
|
20
|
+
|
|
21
|
+
expect({
|
|
22
|
+
scalar: primitive.scalar.shape,
|
|
23
|
+
boolean: primitive.enabled.shape,
|
|
24
|
+
enum: primitive.mode.shape,
|
|
25
|
+
vector: primitive.vector.shape,
|
|
26
|
+
quaternion: primitive.quaternion.shape,
|
|
27
|
+
array: primitive.values.shape,
|
|
28
|
+
'asset-ref': primitive.material.shape,
|
|
29
|
+
optional: container.optionalEntity.shape,
|
|
30
|
+
nested: container.nestedState.shape,
|
|
31
|
+
}).toEqual({
|
|
32
|
+
scalar: 'scalar',
|
|
33
|
+
boolean: 'boolean',
|
|
34
|
+
enum: 'enum',
|
|
35
|
+
vector: 'vector',
|
|
36
|
+
quaternion: 'quaternion',
|
|
37
|
+
array: 'array',
|
|
38
|
+
'asset-ref': 'asset-ref',
|
|
39
|
+
optional: 'optional',
|
|
40
|
+
nested: 'nested',
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('keeps storage type, defaults, and enum labels alongside the shape tag', () => {
|
|
45
|
+
expect(componentSchema(FieldShapePrimitiveFixture)).toMatchObject({
|
|
46
|
+
scalar: 'f32',
|
|
47
|
+
enabled: 'bool',
|
|
48
|
+
mode: 'enum',
|
|
49
|
+
vector: 'array<f32, 3>',
|
|
50
|
+
quaternion: 'array<f32, 4>',
|
|
51
|
+
values: 'array<f32>',
|
|
52
|
+
material: 'shared<MaterialAsset>',
|
|
53
|
+
});
|
|
54
|
+
expect(componentDefinition(FieldShapePrimitiveFixture).defaults).toMatchObject({
|
|
55
|
+
scalar: 0,
|
|
56
|
+
enabled: false,
|
|
57
|
+
mode: 0,
|
|
58
|
+
});
|
|
59
|
+
expect(FieldShapePrimitiveFixture.fields.mode.labels).toEqual({ idle: 0, active: 1 });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// feat-20260709-transform-serialization-vec-fields-and-field-trans M1 / w1:
|
|
2
|
+
// Field-level transient reflection (AC-01, TDD red-first).
|
|
3
|
+
//
|
|
4
|
+
// AC-01: after a component declares a field with `transient: true`, the fact is
|
|
5
|
+
// readable at runtime off `component.fields[field].transient === true` through
|
|
6
|
+
// the FieldReflection layer (component.ts:1048-1052). A field without a
|
|
7
|
+
// transient declaration reflects `transient` as absent/undefined (control), the
|
|
8
|
+
// same shape as the component-level `transient` reflection (D-5 same-word-same-
|
|
9
|
+
// meaning, granularity sunk to field level).
|
|
10
|
+
//
|
|
11
|
+
// Application-point discipline (task w1 rationale): AC-01 is asserted on a real
|
|
12
|
+
// `defineComponent` product's FieldReflection, NOT via a standalone *.test-d.ts.
|
|
13
|
+
// This file uses runtime `defineComponent` calls + value assertions on
|
|
14
|
+
// `.fields[field].transient`.
|
|
15
|
+
|
|
16
|
+
import { describe, expect, it } from 'vitest';
|
|
17
|
+
import { type Component, defineComponent } from '../component';
|
|
18
|
+
|
|
19
|
+
// `transient` is a field-level reflection fact (w2 adds it to FieldReflection).
|
|
20
|
+
// Read it off the concrete component product without widening to `any`.
|
|
21
|
+
function fieldTransient(comp: Component, field: string): boolean | undefined {
|
|
22
|
+
const reflection = comp.fields[field as keyof typeof comp.fields] as
|
|
23
|
+
| { readonly transient?: boolean }
|
|
24
|
+
| undefined;
|
|
25
|
+
return reflection?.transient;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('w1 — field-level transient reflection (AC-01)', () => {
|
|
29
|
+
it('(a) a field declared transient:true reflects transient===true at runtime', () => {
|
|
30
|
+
const C = defineComponent('W1_FieldTransientA', {
|
|
31
|
+
keep: { type: 'f32', default: 0 },
|
|
32
|
+
derived: { type: 'array<f32, 16>', default: new Float32Array(16), transient: true },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Runtime-readable through FieldReflection (component.ts:1048-1052).
|
|
36
|
+
expect(fieldTransient(C, 'derived')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('(b) a field without a transient declaration reflects transient as absent (control)', () => {
|
|
40
|
+
const C = defineComponent('W1_FieldTransientB', {
|
|
41
|
+
keep: { type: 'f32', default: 0 },
|
|
42
|
+
derived: { type: 'array<f32, 16>', default: new Float32Array(16), transient: true },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// exactOptionalPropertyTypes: undeclared fields never carry an explicit
|
|
46
|
+
// transient key -- absence (undefined), not `false`.
|
|
47
|
+
expect(fieldTransient(C, 'keep')).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('(c) bare-keyword field form leaves transient absent', () => {
|
|
51
|
+
const C = defineComponent('W1_FieldTransientC', {
|
|
52
|
+
x: 'f32',
|
|
53
|
+
y: 'f32',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(fieldTransient(C, 'x')).toBeUndefined();
|
|
57
|
+
expect(fieldTransient(C, 'y')).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|