@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,137 @@
|
|
|
1
|
+
// Compile-time type-derivation tests for the array<T,N> / array<T> schema-vocab
|
|
2
|
+
// keywords (w12, AC-01 / AC-02 / AC-03 / AC-04).
|
|
3
|
+
//
|
|
4
|
+
// Locks four invariants:
|
|
5
|
+
//
|
|
6
|
+
// 1. Inference (AC-01): defineComponent('Foo', { entities: { type: 'array<entity>' } })
|
|
7
|
+
// derives `entities` to a `Uint32Array` (the Entity column storage);
|
|
8
|
+
// defineComponent('Bar', { mat: { type: 'array<f32, 16>' } }) derives `mat` to a
|
|
9
|
+
// `Float32Array` (fixed-capacity TypedArray snapshot).
|
|
10
|
+
// Inference holds at three application points: (a) world.addSystem fn
|
|
11
|
+
// callback; (b) QueryRow loop; (c) direct world.get call site.
|
|
12
|
+
//
|
|
13
|
+
// 2. Cross-shape (AC-02): both `array<T>` and `array<T, N>` resolve to a
|
|
14
|
+
// concrete `TypedArrayFor<T>` surface (no intermediate view-class
|
|
15
|
+
// wrapper). Mutation flows through `world.push` / `world.pop` /
|
|
16
|
+
// `world.set`; the TypedArray itself is a read-only snapshot.
|
|
17
|
+
//
|
|
18
|
+
// 3. Element-type (AC-03): T must be scalar + entity. Schema strings
|
|
19
|
+
// `'array<ref<X>>' / 'array<shared<X>>' / 'array<buffer<8>>' /
|
|
20
|
+
// 'array<array<f32,4>>'` are TS compile-time errors when used as a
|
|
21
|
+
// schema field value.
|
|
22
|
+
//
|
|
23
|
+
// 4. Cross-brand (AC-04): the value returned by `snap[i]` for an
|
|
24
|
+
// `'array<entity>'` field is the underlying `number` packed Entity bit
|
|
25
|
+
// pattern; assigning it directly to a `Handle<Mesh, 'unique'>`
|
|
26
|
+
// parameter is a TS compile-time error.
|
|
27
|
+
|
|
28
|
+
import type { Handle } from '@forgeax/engine-types';
|
|
29
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
30
|
+
import type {
|
|
31
|
+
defineComponent,
|
|
32
|
+
FieldValueType,
|
|
33
|
+
SchemaFieldType,
|
|
34
|
+
SchemaOf,
|
|
35
|
+
ShapeOf,
|
|
36
|
+
TypedArrayFor,
|
|
37
|
+
} from '../component';
|
|
38
|
+
import type { World } from '../world';
|
|
39
|
+
|
|
40
|
+
describe('array vocab - keyword recognition (w12, AC-01)', () => {
|
|
41
|
+
it('array<entity> derives to Uint32Array', () => {
|
|
42
|
+
expectTypeOf<FieldValueType<'array<entity>'>>().toEqualTypeOf<Uint32Array>();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('array<f32, 16> derives to Float32Array', () => {
|
|
46
|
+
expectTypeOf<FieldValueType<'array<f32, 16>'>>().toEqualTypeOf<Float32Array>();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('array<u32> derives to Uint32Array', () => {
|
|
50
|
+
expectTypeOf<FieldValueType<'array<u32>'>>().toEqualTypeOf<Uint32Array>();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('array<i32, 4> derives to Int32Array', () => {
|
|
54
|
+
expectTypeOf<FieldValueType<'array<i32, 4>'>>().toEqualTypeOf<Int32Array>();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('array<bool> derives to Uint8Array', () => {
|
|
58
|
+
expectTypeOf<FieldValueType<'array<bool>'>>().toEqualTypeOf<Uint8Array>();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('ShapeOf threads array vocab through FieldValueType', () => {
|
|
62
|
+
type S = {
|
|
63
|
+
entities: 'array<entity>';
|
|
64
|
+
mat: 'array<f32, 16>';
|
|
65
|
+
indices: 'array<u32>';
|
|
66
|
+
};
|
|
67
|
+
type Expected = {
|
|
68
|
+
entities: Uint32Array;
|
|
69
|
+
mat: Float32Array;
|
|
70
|
+
indices: Uint32Array;
|
|
71
|
+
};
|
|
72
|
+
expectTypeOf<ShapeOf<S>>().toEqualTypeOf<Expected>();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('array vocab - three-application-point inference (w12, AC-01 / AC-02)', () => {
|
|
77
|
+
it('application point (a) - inside world.addSystem fn callback', () => {
|
|
78
|
+
type Foo = ReturnType<typeof defineComponent<'Foo', { entities: 'array<entity>' }>>;
|
|
79
|
+
type FooShape = ShapeOf<SchemaOf<Foo>>;
|
|
80
|
+
expectTypeOf<FooShape['entities']>().toEqualTypeOf<Uint32Array>();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('application point (b) - inside a QueryRow loop', () => {
|
|
84
|
+
type Foo = ReturnType<typeof defineComponent<'Foo', { entities: 'array<entity>' }>>;
|
|
85
|
+
type FooShape = ShapeOf<SchemaOf<Foo>>;
|
|
86
|
+
expectTypeOf<FooShape['entities']>().toEqualTypeOf<Uint32Array>();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('application point (c) - direct world.get call site', () => {
|
|
90
|
+
type Foo = ReturnType<typeof defineComponent<'Foo', { entities: 'array<entity>' }>>;
|
|
91
|
+
type FooShape = ShapeOf<SchemaOf<Foo>>;
|
|
92
|
+
// expectType: direct world.get(e, Foo).unwrap().entities matches TypedArrayFor<'u32'>.
|
|
93
|
+
const arr: TypedArrayFor<'u32'> = new Uint32Array(0);
|
|
94
|
+
expectTypeOf(arr).toEqualTypeOf<FooShape['entities']>();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('World type still resolves (no parser regression)', () => {
|
|
98
|
+
expectTypeOf<World>().toBeObject();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('array vocab - element-type rejection (w12, AC-03)', () => {
|
|
103
|
+
it('array<ref<X>> is not a recognised schema field type', () => {
|
|
104
|
+
// @ts-expect-error 'array<ref<X>>' is not a SchemaFieldType (AC-03 element-type).
|
|
105
|
+
const bad: SchemaFieldType = 'array<ref<MaterialAsset>>';
|
|
106
|
+
void bad;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('array<handle<X>> is now a recognised schema field type (feat-20260608 M2 D-1)', () => {
|
|
110
|
+
const valid: SchemaFieldType = 'array<shared<MeshAsset>>';
|
|
111
|
+
void valid;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('array<buffer<N>> is not a recognised schema field type', () => {
|
|
115
|
+
// @ts-expect-error 'array<buffer<8>>' is not a SchemaFieldType (AC-03 element-type).
|
|
116
|
+
const bad: SchemaFieldType = 'array<buffer<8>>';
|
|
117
|
+
void bad;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('array<array<f32,4>> nesting is not a recognised schema field type', () => {
|
|
121
|
+
// @ts-expect-error 'array<array<f32,4>>' is not a SchemaFieldType (AC-03 element-type).
|
|
122
|
+
const bad: SchemaFieldType = 'array<array<f32,4>>';
|
|
123
|
+
void bad;
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe('array vocab - cross-brand rejection (w12, AC-04)', () => {
|
|
128
|
+
it("snap[i] is number; not assignable to Handle<Mesh, 'unique'>", () => {
|
|
129
|
+
const takesMeshHandle = (h: Handle<'Mesh', 'unique'>): void => {
|
|
130
|
+
void h;
|
|
131
|
+
};
|
|
132
|
+
const view: TypedArrayFor<'u32'> = new Uint32Array(1);
|
|
133
|
+
const elem: number = view[0] ?? 0;
|
|
134
|
+
// @ts-expect-error number is not assignable to Handle<'Mesh','unique'> (AC-04 cross-brand).
|
|
135
|
+
takesMeshHandle(elem);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Update } from '../schedule-token';
|
|
2
|
+
// AC-4 minimal-example compile-assertion vehicle.
|
|
3
|
+
//
|
|
4
|
+
// Pastes requirements §10.4 "30-second onboarding minimal example" into a
|
|
5
|
+
// dedicated file so `tsc -b` (and vitest typecheck) certify, every release,
|
|
6
|
+
// that an AI user can write the bundle-field access pattern WITHOUT any `as`
|
|
7
|
+
// casts and WITHOUT explicit `as const` annotations.
|
|
8
|
+
//
|
|
9
|
+
// Note: requirements §10.4 prose uses `createWorld()`. The shipped API is
|
|
10
|
+
// `new World()`; the load-bearing assertion is the bundle field access shape
|
|
11
|
+
// (KD-1 + KD-2 + KD-3 chain), not the constructor form. The minimal-example
|
|
12
|
+
// JSDoc on `SystemDescriptor` (w17) and the CHANGELOG migration block (w18)
|
|
13
|
+
// follow the same shape so the three discovery surfaces stay congruent
|
|
14
|
+
// (charter proposition 1: same signal, three entry points).
|
|
15
|
+
|
|
16
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
17
|
+
import { defineComponent } from '../component';
|
|
18
|
+
import { World } from '../world';
|
|
19
|
+
|
|
20
|
+
describe('[w14] AC-4 — 30-second onboarding minimal example', () => {
|
|
21
|
+
it('bundle field access compiles without `as` casts', () => {
|
|
22
|
+
const Position = defineComponent('Position', { x: { type: 'f32' }, y: { type: 'f32' } });
|
|
23
|
+
const Velocity = defineComponent('Velocity', { dx: { type: 'f32' }, dy: { type: 'f32' } });
|
|
24
|
+
|
|
25
|
+
const world = new World();
|
|
26
|
+
world.addSystem(Update, {
|
|
27
|
+
name: 'movement',
|
|
28
|
+
queries: [{ read: [Velocity], write: [Position] }],
|
|
29
|
+
fn: (_world, queryResults, _commands) => {
|
|
30
|
+
for (const row of queryResults[0]) {
|
|
31
|
+
const velocity = row.get(Velocity);
|
|
32
|
+
const position = row.mut(Position);
|
|
33
|
+
expectTypeOf(position.x).toBeNumber();
|
|
34
|
+
expectTypeOf(velocity.dx).toBeNumber();
|
|
35
|
+
position.x += velocity.dx;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expectTypeOf(world).toMatchTypeOf<World>();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Update } from '../schedule-token';
|
|
2
|
+
// w7 --- 3-point AC-02: world.get(e, C).unwrap().value infers as `string`.
|
|
3
|
+
//
|
|
4
|
+
// Locks AC-02 from requirements: AI users see a native JS `string` value
|
|
5
|
+
// (not StringView, not a wrapper) when reading a `'string'` schema-vocab
|
|
6
|
+
// field at three application points:
|
|
7
|
+
//
|
|
8
|
+
// (a) inside `world.addSystem(Update, { fn })` callback;
|
|
9
|
+
// (b) inside a QueryRow loop;
|
|
10
|
+
// (c) at top-level after a direct import.
|
|
11
|
+
//
|
|
12
|
+
// All three sites pass through the same `FieldValueType<'string'> = string`
|
|
13
|
+
// derivation locked by w9 / w12; this file pins the inference at the
|
|
14
|
+
// observable read shape, with zero `as` assertions.
|
|
15
|
+
//
|
|
16
|
+
// Uses an inline `defineComponent('TestName', { value: 'string' })` instead
|
|
17
|
+
// of the public `Name` token: `Name` was migrated to `@forgeax/engine-runtime`
|
|
18
|
+
// (tweak-20260612-ecs-concept-compression -- it is a built-in component, not
|
|
19
|
+
// part of the ECS framework itself). The ECS-internal type test exercises the
|
|
20
|
+
// `'string'` schema-vocab keyword directly to avoid an upstream import
|
|
21
|
+
// dependency on the runtime sibling.
|
|
22
|
+
|
|
23
|
+
import { describe, expectTypeOf, it } from 'vitest';
|
|
24
|
+
import { defineComponent } from '../component';
|
|
25
|
+
import { World } from '../world';
|
|
26
|
+
|
|
27
|
+
const TestName = defineComponent('TestName', { value: { type: 'string' } });
|
|
28
|
+
|
|
29
|
+
describe('w7 --- (a) inside world.addSystem fn callback (AC-02)', () => {
|
|
30
|
+
it('world.get(e, TestName).unwrap().value infers as string', () => {
|
|
31
|
+
const w = new World();
|
|
32
|
+
const e = w.spawn({ component: TestName, data: { value: 'Player' } }).unwrap();
|
|
33
|
+
|
|
34
|
+
w.addSystem(Update, {
|
|
35
|
+
name: 'reader',
|
|
36
|
+
queries: [],
|
|
37
|
+
fn: () => {
|
|
38
|
+
const value = w.get(e, TestName).unwrap().value;
|
|
39
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('w7 --- (b) inside a QueryRow loop (AC-02)', () => {
|
|
46
|
+
it('world.get(e, TestName).unwrap().value infers as string from a query callback', () => {
|
|
47
|
+
const w = new World();
|
|
48
|
+
const e = w.spawn({ component: TestName, data: { value: 'Boss' } }).unwrap();
|
|
49
|
+
|
|
50
|
+
w.addSystem(Update, {
|
|
51
|
+
name: 'queryReader',
|
|
52
|
+
queries: [{ with: [TestName] }],
|
|
53
|
+
fn: (_world, results) => {
|
|
54
|
+
for (const result of results) {
|
|
55
|
+
for (const _bundle of result) {
|
|
56
|
+
const value = w.get(e, TestName).unwrap().value;
|
|
57
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('w7 --- (c) top-level direct import (AC-02)', () => {
|
|
66
|
+
it('world.get(e, TestName).unwrap().value infers as string at top-level', () => {
|
|
67
|
+
const w = new World();
|
|
68
|
+
const e = w.spawn({ component: TestName, data: { value: 'Hero' } }).unwrap();
|
|
69
|
+
const value = w.get(e, TestName).unwrap().value;
|
|
70
|
+
expectTypeOf(value).toEqualTypeOf<string>();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createWorldContext } from '../plugin-service';
|
|
3
|
+
import { World } from '../world';
|
|
4
|
+
|
|
5
|
+
describe('world plugin service', () => {
|
|
6
|
+
it('creates a Cordis context with the World owner', async () => {
|
|
7
|
+
const world = new World();
|
|
8
|
+
const context = await createWorldContext(world);
|
|
9
|
+
|
|
10
|
+
expect(context).toBeDefined();
|
|
11
|
+
await context.fiber.dispose();
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import { World } from '../world';
|
|
4
|
+
|
|
5
|
+
const Position = defineComponent('CombinationPosition', { x: 'f32' });
|
|
6
|
+
|
|
7
|
+
describe('Query.combinations', () => {
|
|
8
|
+
it('uses the same filtered entity order as row iteration', () => {
|
|
9
|
+
const world = new World();
|
|
10
|
+
const entities = [1, 2, 3].map((x) =>
|
|
11
|
+
world.spawn({ component: Position, data: { x } }).unwrap(),
|
|
12
|
+
);
|
|
13
|
+
const query = world.query({ read: [Position] }).unwrap();
|
|
14
|
+
const pairs = [...query.combinations()].map((rows) => rows.map((row) => row.entity));
|
|
15
|
+
expect(pairs).toEqual([
|
|
16
|
+
[entities[0], entities[1]],
|
|
17
|
+
[entities[0], entities[2]],
|
|
18
|
+
[entities[1], entities[2]],
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('supports k-tuples and yields none when k exceeds the match count', () => {
|
|
23
|
+
const world = new World();
|
|
24
|
+
for (const x of [1, 2, 3]) world.spawn({ component: Position, data: { x } }).unwrap();
|
|
25
|
+
const query = world.query({ read: [Position] }).unwrap();
|
|
26
|
+
expect([...query.combinations(3)]).toHaveLength(1);
|
|
27
|
+
expect([...query.combinations(4)]).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('does not commit change observation when combination iteration stops early', () => {
|
|
31
|
+
const world = new World();
|
|
32
|
+
const entities = [1, 2, 3].map((x) =>
|
|
33
|
+
world.spawn({ component: Position, data: { x } }).unwrap(),
|
|
34
|
+
);
|
|
35
|
+
const query = world.query({ read: [Position], changed: [Position] }).unwrap();
|
|
36
|
+
const iterator = query.combinations(2)[Symbol.iterator]();
|
|
37
|
+
expect(iterator.next().done).toBe(false);
|
|
38
|
+
iterator.return?.();
|
|
39
|
+
|
|
40
|
+
expect(Array.from(query, (row) => row.entity)).toEqual(entities);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// feat-20260709 M2 / w6: light-extract flat-column A/B perf ratio bench (AC-10, D-2/D-6).
|
|
2
|
+
//
|
|
3
|
+
// Independent of the TRS bench (query-trs-flat-column-ratio.perf.test.ts, D-2):
|
|
4
|
+
// light extract is a distinct hot path (q7) -- it multiplies each color lane by
|
|
5
|
+
// intensity while reading direction, so its per-row work differs from the
|
|
6
|
+
// propagate/getField path. This bench models the render-system-extract light
|
|
7
|
+
// loop over QuerySpan columns, NOT the TRS path, and does not reuse its
|
|
8
|
+
// baseline.
|
|
9
|
+
//
|
|
10
|
+
// In-process ratio gate, NOT absolute wallclock (memory
|
|
11
|
+
// absolute-wallclock-perf-gate-flakes-use-in-process-ratio): the same process,
|
|
12
|
+
// same JIT tier, and same allocator time both sides, so the candidate/baseline
|
|
13
|
+
// ratio isolates the column-shape effect from host noise.
|
|
14
|
+
//
|
|
15
|
+
// Baseline: directionX/Y/Z + colorR/G/B + intensity (7 per-axis f32 scalar
|
|
16
|
+
// columns, the pre-migration light shape, synthesized here so the
|
|
17
|
+
// gate survives the migration).
|
|
18
|
+
// Candidate: direction array<f32,3> / color array<f32,3> + intensity (the
|
|
19
|
+
// post-migration shape), read via the query bundle's flat stride-N
|
|
20
|
+
// subarray -- direction[i*3+a] / color[i*3+a], zero per-call
|
|
21
|
+
// allocation (research Finding 5 adjudication table, allowed row;
|
|
22
|
+
// D-6 forbids .get() materialize / _getArrayView per-call alloc).
|
|
23
|
+
//
|
|
24
|
+
// Gate: ratio = candidate_min / baseline_min <= 1.15 (N=10_000 entities,
|
|
25
|
+
// min-of-5 rounds, sides alternate per round to cancel warm-up drift).
|
|
26
|
+
// Expected ratio is ~1.0 or below (contiguous 3-wide lanes have better locality
|
|
27
|
+
// than 7 scattered columns); 1.15 is anti-noise headroom, not a concession.
|
|
28
|
+
//
|
|
29
|
+
// Falsifiability (plan-strategy 5.4, one-time manual check, not in CI):
|
|
30
|
+
// swapping the candidate reader to the world.get(e, C).color materializing path
|
|
31
|
+
// blows the ratio past the gate (precedent magnitude ~51) -- verified once by hand
|
|
32
|
+
// during w6 (see milestone report), then restored to the flat form below.
|
|
33
|
+
|
|
34
|
+
import { describe, expect, it } from 'vitest';
|
|
35
|
+
import { defineComponent } from '../component';
|
|
36
|
+
import { World } from '../world';
|
|
37
|
+
import { worldInternal } from '../world-internal';
|
|
38
|
+
|
|
39
|
+
const N = 10_000;
|
|
40
|
+
const ROUNDS = 5;
|
|
41
|
+
const INNER_REPEATS = 30;
|
|
42
|
+
const RATIO_GATE = 1.15;
|
|
43
|
+
|
|
44
|
+
const BaselineLight7 = defineComponent('W6_BaselineLight7', {
|
|
45
|
+
directionX: 'f32',
|
|
46
|
+
directionY: 'f32',
|
|
47
|
+
directionZ: 'f32',
|
|
48
|
+
colorR: 'f32',
|
|
49
|
+
colorG: 'f32',
|
|
50
|
+
colorB: 'f32',
|
|
51
|
+
intensity: 'f32',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const CandidateLightVec = defineComponent('W6_CandidateLightVec', {
|
|
55
|
+
direction: { type: 'array<f32, 3>', default: new Float32Array(3) },
|
|
56
|
+
color: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },
|
|
57
|
+
intensity: { type: 'f32', default: 1 },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function spawnBaselineWorld(): World {
|
|
61
|
+
const world = new World();
|
|
62
|
+
for (let i = 0; i < N; i++) {
|
|
63
|
+
world.spawn({
|
|
64
|
+
component: BaselineLight7,
|
|
65
|
+
data: {
|
|
66
|
+
directionX: i,
|
|
67
|
+
directionY: i * 2,
|
|
68
|
+
directionZ: i * 3,
|
|
69
|
+
colorR: 0.5,
|
|
70
|
+
colorG: 0.6,
|
|
71
|
+
colorB: 0.7,
|
|
72
|
+
intensity: 2,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return world;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function spawnCandidateWorld(): World {
|
|
80
|
+
const world = new World();
|
|
81
|
+
for (let i = 0; i < N; i++) {
|
|
82
|
+
world.spawn({
|
|
83
|
+
component: CandidateLightVec,
|
|
84
|
+
data: { direction: [i, i * 2, i * 3], color: [0.5, 0.6, 0.7], intensity: 2 },
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return world;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Preallocated snapshot sinks (mirrors extract writing into a fixed array).
|
|
91
|
+
const snapDir = new Float32Array(N * 3);
|
|
92
|
+
const snapColor = new Float32Array(N * 3);
|
|
93
|
+
|
|
94
|
+
/** Extract-shaped loop over per-axis scalar columns. */
|
|
95
|
+
function traverseBaseline(world: World): number {
|
|
96
|
+
const query = world.query({ read: [BaselineLight7] }).unwrap();
|
|
97
|
+
let sum = 0;
|
|
98
|
+
for (let rep = 0; rep < INNER_REPEATS; rep++) {
|
|
99
|
+
for (const span of query.spans().unwrap()) {
|
|
100
|
+
const b = span.get(BaselineLight7);
|
|
101
|
+
const n = span.length;
|
|
102
|
+
for (let i = 0; i < n; i++) {
|
|
103
|
+
const intensity = b.intensity[i] as number;
|
|
104
|
+
snapDir[i * 3] = b.directionX[i] as number;
|
|
105
|
+
snapDir[i * 3 + 1] = b.directionY[i] as number;
|
|
106
|
+
snapDir[i * 3 + 2] = b.directionZ[i] as number;
|
|
107
|
+
snapColor[i * 3] = (b.colorR[i] as number) * intensity;
|
|
108
|
+
snapColor[i * 3 + 1] = (b.colorG[i] as number) * intensity;
|
|
109
|
+
snapColor[i * 3 + 2] = (b.colorB[i] as number) * intensity;
|
|
110
|
+
sum += (snapDir[i * 3] as number) + (snapColor[i * 3] as number);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return sum;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Extract-shaped loop over flat stride-N array columns (D-6 hot-path form). */
|
|
118
|
+
function traverseCandidate(world: World): number {
|
|
119
|
+
const query = world.query({ read: [CandidateLightVec] }).unwrap();
|
|
120
|
+
let sum = 0;
|
|
121
|
+
for (let rep = 0; rep < INNER_REPEATS; rep++) {
|
|
122
|
+
for (const span of query.spans().unwrap()) {
|
|
123
|
+
const c = span.get(CandidateLightVec);
|
|
124
|
+
const direction = c.direction;
|
|
125
|
+
const color = c.color;
|
|
126
|
+
const n = span.length;
|
|
127
|
+
for (let i = 0; i < n; i++) {
|
|
128
|
+
const intensity = c.intensity[i] as number;
|
|
129
|
+
snapDir[i * 3] = direction[i * 3] as number;
|
|
130
|
+
snapDir[i * 3 + 1] = direction[i * 3 + 1] as number;
|
|
131
|
+
snapDir[i * 3 + 2] = direction[i * 3 + 2] as number;
|
|
132
|
+
snapColor[i * 3] = (color[i * 3] as number) * intensity;
|
|
133
|
+
snapColor[i * 3 + 1] = (color[i * 3 + 1] as number) * intensity;
|
|
134
|
+
snapColor[i * 3 + 2] = (color[i * 3 + 2] as number) * intensity;
|
|
135
|
+
sum += (snapDir[i * 3] as number) + (snapColor[i * 3] as number);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return sum;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
describe('w6 -- light-extract flat-column A/B perf ratio (AC-10)', () => {
|
|
143
|
+
it(`candidate/baseline extract ratio stays <= ${RATIO_GATE} (min-of-${ROUNDS}, N=${N})`, () => {
|
|
144
|
+
const baselineWorld = spawnBaselineWorld();
|
|
145
|
+
const candidateWorld = spawnCandidateWorld();
|
|
146
|
+
|
|
147
|
+
// Warm-up: bring both traversal fns to the same JIT tier before timing.
|
|
148
|
+
let sink = 0;
|
|
149
|
+
sink += traverseBaseline(baselineWorld);
|
|
150
|
+
sink += traverseCandidate(candidateWorld);
|
|
151
|
+
|
|
152
|
+
const baselineTimes: number[] = [];
|
|
153
|
+
const candidateTimes: number[] = [];
|
|
154
|
+
|
|
155
|
+
for (let round = 0; round < ROUNDS; round++) {
|
|
156
|
+
// Alternate side order per round to cancel residual warm-up drift.
|
|
157
|
+
const baselineFirst = round % 2 === 0;
|
|
158
|
+
if (baselineFirst) {
|
|
159
|
+
const b0 = performance.now();
|
|
160
|
+
sink += traverseBaseline(baselineWorld);
|
|
161
|
+
baselineTimes.push(performance.now() - b0);
|
|
162
|
+
const c0 = performance.now();
|
|
163
|
+
sink += traverseCandidate(candidateWorld);
|
|
164
|
+
candidateTimes.push(performance.now() - c0);
|
|
165
|
+
} else {
|
|
166
|
+
const c0 = performance.now();
|
|
167
|
+
sink += traverseCandidate(candidateWorld);
|
|
168
|
+
candidateTimes.push(performance.now() - c0);
|
|
169
|
+
const b0 = performance.now();
|
|
170
|
+
sink += traverseBaseline(baselineWorld);
|
|
171
|
+
baselineTimes.push(performance.now() - b0);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Both sides write identical values -- keeps the loops from being
|
|
176
|
+
// dead-code-eliminated and doubles as a smoke correctness check.
|
|
177
|
+
expect(sink).toBeGreaterThan(0);
|
|
178
|
+
|
|
179
|
+
const baselineMin = Math.min(...baselineTimes);
|
|
180
|
+
const candidateMin = Math.min(...candidateTimes);
|
|
181
|
+
const ratio = candidateMin / baselineMin;
|
|
182
|
+
|
|
183
|
+
// biome-ignore lint/suspicious/noConsole: surface the measured ratio in test output for AC-10 review
|
|
184
|
+
console.info(
|
|
185
|
+
`[w6 perf ratio] baseline_min=${baselineMin.toFixed(3)}ms candidate_min=${candidateMin.toFixed(3)}ms ratio=${ratio.toFixed(3)}`,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
expect(ratio).toBeLessThanOrEqual(RATIO_GATE);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('M5 -- wide table versus sparse tag-flip trend', () => {
|
|
193
|
+
const widths = [1, 4, 8, 16] as const;
|
|
194
|
+
const TableFlipTag = defineComponent('M5WideTableFlipTag', {});
|
|
195
|
+
const SparseFlipTag = defineComponent('M5WideSparseFlipTag', {}, { storage: 'sparse' });
|
|
196
|
+
|
|
197
|
+
function makeWideComponent(width: number) {
|
|
198
|
+
const schema: Record<string, 'f32'> = {};
|
|
199
|
+
for (let index = 0; index < width; index++) schema[`f${index}`] = 'f32';
|
|
200
|
+
return defineComponent(`M1WideFlip${width}`, schema);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function bytesIn(table: {
|
|
204
|
+
storage: Map<number, { fields: Map<string, { buffer: ArrayBufferLike }> }>;
|
|
205
|
+
}): number {
|
|
206
|
+
let bytes = 0;
|
|
207
|
+
for (const { fields } of table.storage.values()) {
|
|
208
|
+
for (const column of fields.values()) bytes += column.buffer.byteLength;
|
|
209
|
+
}
|
|
210
|
+
return bytes;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
it('keeps sparse copied bytes flat at zero while table migration grows with arity', () => {
|
|
214
|
+
const tableCopiedBytes: number[] = [];
|
|
215
|
+
const sparseCopiedBytes: number[] = [];
|
|
216
|
+
for (const width of widths) {
|
|
217
|
+
const wide = makeWideComponent(width);
|
|
218
|
+
const tableWorld = new World();
|
|
219
|
+
const tableEntity = tableWorld.spawn({ component: wide, data: {} as never }).unwrap();
|
|
220
|
+
const tableBefore = tableWorld[worldInternal].getGraph().tables[0];
|
|
221
|
+
if (tableBefore === undefined) throw new Error('source table missing');
|
|
222
|
+
tableCopiedBytes.push(bytesIn(tableBefore));
|
|
223
|
+
tableWorld.addComponent(tableEntity, { component: TableFlipTag, data: {} }).unwrap();
|
|
224
|
+
|
|
225
|
+
const sparseWorld = new World();
|
|
226
|
+
const sparseEntity = sparseWorld.spawn({ component: wide, data: {} as never }).unwrap();
|
|
227
|
+
const sparseBefore = sparseWorld[worldInternal].getGraph().tables[0];
|
|
228
|
+
if (sparseBefore === undefined) throw new Error('source table missing');
|
|
229
|
+
sparseWorld.addComponent(sparseEntity, { component: SparseFlipTag, data: {} }).unwrap();
|
|
230
|
+
const sparseAfter = sparseWorld[worldInternal].getGraph().tables[0];
|
|
231
|
+
sparseCopiedBytes.push(sparseAfter === sparseBefore ? 0 : bytesIn(sparseBefore));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// biome-ignore lint/suspicious/noConsole: the trend is the characterization output
|
|
235
|
+
console.info(
|
|
236
|
+
`[m5 tag-flip] widths=${widths.join(',')} tableCopiedBytes=${tableCopiedBytes.join(',')} sparseCopiedBytes=${sparseCopiedBytes.join(',')}`,
|
|
237
|
+
);
|
|
238
|
+
const [first, second, third, fourth] = tableCopiedBytes;
|
|
239
|
+
if (
|
|
240
|
+
first === undefined ||
|
|
241
|
+
second === undefined ||
|
|
242
|
+
third === undefined ||
|
|
243
|
+
fourth === undefined
|
|
244
|
+
) {
|
|
245
|
+
throw new Error('wide-tag trend did not produce four samples');
|
|
246
|
+
}
|
|
247
|
+
expect(first).toBeLessThan(second);
|
|
248
|
+
expect(second).toBeLessThan(third);
|
|
249
|
+
expect(third).toBeLessThan(fourth);
|
|
250
|
+
expect(sparseCopiedBytes).toEqual([0, 0, 0, 0]);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { defineComponent } from '../component';
|
|
3
|
+
import { World } from '../world';
|
|
4
|
+
|
|
5
|
+
const ENTITY_COUNT = 2_000;
|
|
6
|
+
const ROUNDS = 3;
|
|
7
|
+
|
|
8
|
+
const TrendValue = defineComponent('StorageTrendValue', { value: 'f32' });
|
|
9
|
+
const TrendSelected = defineComponent('StorageTrendSelected', {}, { storage: 'sparse' });
|
|
10
|
+
|
|
11
|
+
function minimumDuration(run: () => number): { duration: number; sum: number } {
|
|
12
|
+
let duration = Number.POSITIVE_INFINITY;
|
|
13
|
+
let sum = 0;
|
|
14
|
+
for (let round = 0; round < ROUNDS; round++) {
|
|
15
|
+
const start = performance.now();
|
|
16
|
+
sum = run();
|
|
17
|
+
duration = Math.min(duration, performance.now() - start);
|
|
18
|
+
}
|
|
19
|
+
return { duration, sum };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('table, sparse, span, and change-distribution trends', () => {
|
|
23
|
+
it('reports equivalent table-row, sparse-row, and dense-span traversal', () => {
|
|
24
|
+
const world = new World();
|
|
25
|
+
for (let index = 0; index < ENTITY_COUNT; index++) {
|
|
26
|
+
const components = [{ component: TrendValue, data: { value: index + 1 } }] as const;
|
|
27
|
+
if (index % 2 === 0) {
|
|
28
|
+
world.spawn(...components, { component: TrendSelected, data: {} }).unwrap();
|
|
29
|
+
} else {
|
|
30
|
+
world.spawn(...components).unwrap();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tableRows = world.query({ read: [TrendValue] }).unwrap();
|
|
35
|
+
const sparseRows = world.query({ read: [TrendValue], with: [TrendSelected] }).unwrap();
|
|
36
|
+
const denseSpans = world.query({ read: [TrendValue] }).unwrap();
|
|
37
|
+
|
|
38
|
+
const table = minimumDuration(() => {
|
|
39
|
+
let sum = 0;
|
|
40
|
+
for (const row of tableRows) sum += row.get(TrendValue).value;
|
|
41
|
+
return sum;
|
|
42
|
+
});
|
|
43
|
+
const sparse = minimumDuration(() => {
|
|
44
|
+
let sum = 0;
|
|
45
|
+
for (const row of sparseRows) sum += row.get(TrendValue).value;
|
|
46
|
+
return sum;
|
|
47
|
+
});
|
|
48
|
+
const span = minimumDuration(() => {
|
|
49
|
+
let sum = 0;
|
|
50
|
+
for (const range of denseSpans.spans().unwrap()) {
|
|
51
|
+
const values = range.get(TrendValue).value;
|
|
52
|
+
for (let index = 0; index < range.length; index++) sum += values[index] ?? 0;
|
|
53
|
+
}
|
|
54
|
+
return sum;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(table.sum).toBe((ENTITY_COUNT * (ENTITY_COUNT + 1)) / 2);
|
|
58
|
+
expect(sparse.sum).toBe((ENTITY_COUNT / 2) ** 2);
|
|
59
|
+
expect(span.sum).toBe(table.sum);
|
|
60
|
+
expect([...denseSpans.spans().unwrap()]).toHaveLength(1);
|
|
61
|
+
|
|
62
|
+
// biome-ignore lint/suspicious/noConsole: trend output is review evidence, not an absolute gate
|
|
63
|
+
console.info(
|
|
64
|
+
`[storage trend] table-row=${table.duration.toFixed(3)}ms sparse-row=${sparse.duration.toFixed(3)}ms span=${span.duration.toFixed(3)}ms entities=${ENTITY_COUNT}`,
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('observes block and interleaved changes as rows without result fragmentation', () => {
|
|
69
|
+
const world = new World();
|
|
70
|
+
const entities = Array.from({ length: ENTITY_COUNT }, (_, index) =>
|
|
71
|
+
world.spawn({ component: TrendValue, data: { value: index } }).unwrap(),
|
|
72
|
+
);
|
|
73
|
+
const changed = world.query({ changed: [TrendValue] }).unwrap();
|
|
74
|
+
expect([...changed]).toHaveLength(ENTITY_COUNT);
|
|
75
|
+
|
|
76
|
+
for (let index = 0; index < ENTITY_COUNT; index += 2) {
|
|
77
|
+
const entity = entities[index];
|
|
78
|
+
if (entity === undefined) throw new Error('interleaved trend entity missing');
|
|
79
|
+
world.set(entity, TrendValue, { value: index + 1 }).unwrap();
|
|
80
|
+
}
|
|
81
|
+
const interleavedStart = performance.now();
|
|
82
|
+
const interleaved = Array.from(changed, (row) => row.entity);
|
|
83
|
+
const interleavedDuration = performance.now() - interleavedStart;
|
|
84
|
+
|
|
85
|
+
for (let index = 0; index < ENTITY_COUNT / 2; index++) {
|
|
86
|
+
const entity = entities[index];
|
|
87
|
+
if (entity === undefined) throw new Error('block trend entity missing');
|
|
88
|
+
world.set(entity, TrendValue, { value: index + 2 }).unwrap();
|
|
89
|
+
}
|
|
90
|
+
const blockStart = performance.now();
|
|
91
|
+
const block = Array.from(changed, (row) => row.entity);
|
|
92
|
+
const blockDuration = performance.now() - blockStart;
|
|
93
|
+
|
|
94
|
+
expect(interleaved).toHaveLength(ENTITY_COUNT / 2);
|
|
95
|
+
expect(block).toHaveLength(ENTITY_COUNT / 2);
|
|
96
|
+
expect(interleaved.every((entity, index) => entity === entities[index * 2])).toBe(true);
|
|
97
|
+
expect(block.every((entity, index) => entity === entities[index])).toBe(true);
|
|
98
|
+
|
|
99
|
+
// biome-ignore lint/suspicious/noConsole: distribution timing is characterization evidence
|
|
100
|
+
console.info(
|
|
101
|
+
`[change trend] interleaved=${interleavedDuration.toFixed(3)}ms block=${blockDuration.toFixed(3)}ms hits=${ENTITY_COUNT / 2}`,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|