@forgeax/engine-ecs 0.1.2
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 +272 -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-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-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 +214 -0
- package/dist/errors/validation-errors.d.ts.map +1 -0
- package/dist/errors.d.ts +756 -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 +7354 -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 +50 -0
- package/dist/projection/index.d.ts.map +1 -0
- package/dist/projection/index.mjs +517 -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 +264 -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-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 +1452 -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-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 +207 -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 +418 -0
- package/src/errors.ts +1453 -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 +142 -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 +1412 -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 +1397 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —
|
|
3
|
+
* `.code = 'shared-field-invalid-value'`.
|
|
4
|
+
*
|
|
5
|
+
* A `shared<T>` scalar / `array<shared<T>>` element must be a resolved numeric
|
|
6
|
+
* Handle. A raw GUID string / `{ guid }` / `{ kind }` object (the
|
|
7
|
+
* pre-resolution shape a sidecar hands an AI user) used to be silently coerced
|
|
8
|
+
* to the all-zero sentinel by the column packer / scalar write path, so a
|
|
9
|
+
* mis-bound reference read back as `0` / `[0,0,0,0]` and rendered blank with no
|
|
10
|
+
* error. `validateComponentDataKeys` only checks key NAMES, not value types;
|
|
11
|
+
* this error closes the value-type gap at all three write entries
|
|
12
|
+
* (spawn / addComponent / set). `.detail.field` + `.detail.fieldType` name the
|
|
13
|
+
* offending field; `.detail.index` locates the array element (undefined for the
|
|
14
|
+
* scalar form).
|
|
15
|
+
*
|
|
16
|
+
* `.detail = { component, field, fieldType, actualValue, index? }`
|
|
17
|
+
* `.hint` — names the field and points at `AssetRegistry.load + allocSharedRef`.
|
|
18
|
+
*/
|
|
19
|
+
export class SharedFieldInvalidValueError extends Error {
|
|
20
|
+
override readonly name = 'SharedFieldInvalidValueError';
|
|
21
|
+
readonly code = 'shared-field-invalid-value' as const;
|
|
22
|
+
readonly hint: string;
|
|
23
|
+
readonly expected: string;
|
|
24
|
+
readonly detail: {
|
|
25
|
+
readonly component: string;
|
|
26
|
+
readonly field: string;
|
|
27
|
+
readonly fieldType: string;
|
|
28
|
+
readonly actualValue: unknown;
|
|
29
|
+
readonly index?: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
constructor(
|
|
33
|
+
componentName: string,
|
|
34
|
+
fieldName: string,
|
|
35
|
+
fieldType: string,
|
|
36
|
+
actualValue: unknown,
|
|
37
|
+
index?: number,
|
|
38
|
+
) {
|
|
39
|
+
const at = index === undefined ? '' : `[${index}]`;
|
|
40
|
+
const expected = `a resolved numeric Handle for shared field '${fieldName}${at}'`;
|
|
41
|
+
const hint =
|
|
42
|
+
`'${fieldName}${at}' on '${componentName}' is a ${fieldType} reference; ` +
|
|
43
|
+
`got ${typeof actualValue} (${JSON.stringify(actualValue)}). ` +
|
|
44
|
+
`Resolve the GUID to a handle first: AssetRegistry.load(guid, kind) then allocSharedRef(...), ` +
|
|
45
|
+
`and bind the returned numeric handle — not the raw GUID / sidecar object.`;
|
|
46
|
+
super(
|
|
47
|
+
`${componentName}.${fieldName}${at}: shared field bound to a non-handle value.\n` +
|
|
48
|
+
` code: shared-field-invalid-value\n` +
|
|
49
|
+
` component: ${componentName}\n` +
|
|
50
|
+
` field: ${fieldName}${at}\n` +
|
|
51
|
+
` fieldType: ${fieldType}\n` +
|
|
52
|
+
` expected: ${expected}\n` +
|
|
53
|
+
` hint: ${hint}`,
|
|
54
|
+
);
|
|
55
|
+
this.hint = hint;
|
|
56
|
+
this.expected = expected;
|
|
57
|
+
this.detail =
|
|
58
|
+
index === undefined
|
|
59
|
+
? { component: componentName, field: fieldName, fieldType, actualValue }
|
|
60
|
+
: { component: componentName, field: fieldName, fieldType, actualValue, index };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
// feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —
|
|
66
|
+
// closed-union evolution +3 for the SpriteInstances primitive + tilemap
|
|
67
|
+
// terrain static-batch path. AGENTS.md §Error model evolution contract: minor
|
|
68
|
+
// (add member only).
|
|
69
|
+
//
|
|
70
|
+
// All 3 codes are DECLARED here (M1) but FIRED at the render-system-extract
|
|
71
|
+
// QueryRow loop (M3 w13) — plan-strategy D-6 "fail-fast at the render
|
|
72
|
+
// domain entry, not at ECS spawn-time (avoids reverse dep ECS -> AssetRegistry
|
|
73
|
+
// to look up MaterialAsset.shadingModel)". M1 carries class declarations only;
|
|
74
|
+
// the `_routeError` call sites land in M3.
|
|
75
|
+
//
|
|
76
|
+
// Three codes, three failure shapes:
|
|
77
|
+
// - 'sprite-instances-count-mismatch' — transforms.length / 16 !==
|
|
78
|
+
// regions.length / 4 (stride contract; cf. instance-transforms-stride-
|
|
79
|
+
// mismatch which guards Instances stride 16).
|
|
80
|
+
// - 'sprite-instances-requires-sprite-shader' — the entity's MaterialAsset's
|
|
81
|
+
// first pass shader is not 'forgeax::sprite' (extract-time check; AI users
|
|
82
|
+
// using SpriteInstances must pick a sprite-shaded material).
|
|
83
|
+
// - 'sprite-instances-mutually-exclusive-with-instances' — the same entity
|
|
84
|
+
// carries both Instances + SpriteInstances (the two primitives are peers;
|
|
85
|
+
// SpriteInstances supersedes Instances when per-instance UV region is
|
|
86
|
+
// needed).
|
|
87
|
+
//
|
|
88
|
+
// .hint follows charter P3: each contains the literal repair step AI users
|
|
89
|
+
// can paste back into spawn code (transforms/regions stride math; shading
|
|
90
|
+
// model field write; component removal).
|
|
91
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Thrown / returned via Layer-3 error route when `SpriteInstances.transforms`
|
|
95
|
+
* (stride 16 — column-major mat4 per instance) and `SpriteInstances.regions`
|
|
96
|
+
* (stride 4 — per-instance UV vec4) instance counts disagree at render-system-
|
|
97
|
+
* extract entry.
|
|
98
|
+
*
|
|
99
|
+
* `.code = 'sprite-instances-count-mismatch'`
|
|
100
|
+
* `.detail = { transformsLength, regionsLength, expectedStride: { transforms: 16, regions: 4 } }`
|
|
101
|
+
* `.hint` — instructs the AI user to enforce
|
|
102
|
+
* `transforms.length / 16 === regions.length / 4`.
|
|
103
|
+
*/
|
|
104
|
+
export class SpriteInstancesCountMismatchError extends Error {
|
|
105
|
+
override readonly name = 'SpriteInstancesCountMismatchError';
|
|
106
|
+
readonly code = 'sprite-instances-count-mismatch' as const;
|
|
107
|
+
readonly hint: string;
|
|
108
|
+
readonly expected: string;
|
|
109
|
+
readonly detail: {
|
|
110
|
+
readonly code: 'sprite-instances-count-mismatch';
|
|
111
|
+
readonly transformsLength: number;
|
|
112
|
+
readonly regionsLength: number;
|
|
113
|
+
readonly expectedStride: { readonly transforms: 16; readonly regions: 4 };
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
constructor(transformsLength: number, regionsLength: number) {
|
|
117
|
+
const hint =
|
|
118
|
+
'SpriteInstances.transforms (stride 16) and SpriteInstances.regions (stride 4) ' +
|
|
119
|
+
'must describe the same instance count: ensure transforms.length / 16 === regions.length / 4 ' +
|
|
120
|
+
'at the spawn / set site (resize both arrays together).';
|
|
121
|
+
const expected = 'transforms.length / 16 === regions.length / 4';
|
|
122
|
+
super(
|
|
123
|
+
`SpriteInstances: per-instance count mismatch between transforms and regions.\n` +
|
|
124
|
+
` code: sprite-instances-count-mismatch\n` +
|
|
125
|
+
` transformsLength: ${transformsLength} (count = ${transformsLength / 16})\n` +
|
|
126
|
+
` regionsLength: ${regionsLength} (count = ${regionsLength / 4})\n` +
|
|
127
|
+
` expected: ${expected}\n` +
|
|
128
|
+
` hint: ${hint}`,
|
|
129
|
+
);
|
|
130
|
+
this.hint = hint;
|
|
131
|
+
this.expected = expected;
|
|
132
|
+
this.detail = {
|
|
133
|
+
code: 'sprite-instances-count-mismatch',
|
|
134
|
+
transformsLength,
|
|
135
|
+
regionsLength,
|
|
136
|
+
expectedStride: { transforms: 16, regions: 4 },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Thrown / returned via Layer-3 error route when an entity carrying
|
|
143
|
+
* `SpriteInstances` references a MaterialAsset whose first pass shader is not
|
|
144
|
+
* `'forgeax::sprite'`. Detected at render-system-extract entry (M3 w13).
|
|
145
|
+
*
|
|
146
|
+
* `.code = 'sprite-instances-requires-sprite-shader'`
|
|
147
|
+
* `.detail = { entityId, observedMaterialShaderId }`
|
|
148
|
+
* `.hint` — instructs the AI user to bind a MaterialAsset whose first pass
|
|
149
|
+
* `shader` is `'forgeax::sprite'` or `'forgeax::sprite-lit'`.
|
|
150
|
+
*
|
|
151
|
+
* feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t6:
|
|
152
|
+
* sprite-lit walks the same per-instance UV region vertex path as sprite
|
|
153
|
+
* (VsOut byte-identical, paramSchema mirror); both shader ids are accepted.
|
|
154
|
+
*/
|
|
155
|
+
export class SpriteInstancesRequiresSpriteShaderError extends Error {
|
|
156
|
+
override readonly name = 'SpriteInstancesRequiresSpriteShaderError';
|
|
157
|
+
readonly code = 'sprite-instances-requires-sprite-shader' as const;
|
|
158
|
+
readonly hint: string;
|
|
159
|
+
readonly expected: string;
|
|
160
|
+
readonly detail: {
|
|
161
|
+
readonly code: 'sprite-instances-requires-sprite-shader';
|
|
162
|
+
readonly entityId: number;
|
|
163
|
+
readonly observedMaterialShaderId: string;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
constructor(entityId: number, observedMaterialShaderId: string) {
|
|
167
|
+
const hint =
|
|
168
|
+
"bind a MaterialAsset whose first pass `shader` is 'forgeax::sprite' " +
|
|
169
|
+
"or 'forgeax::sprite-lit' to this entity's MeshRenderer (SpriteInstances " +
|
|
170
|
+
'requires a sprite-family shader so the per-instance UV region is consumed ' +
|
|
171
|
+
'by the sprite vertex shader path).';
|
|
172
|
+
const expected =
|
|
173
|
+
"MaterialAsset.passes[0].shader === 'forgeax::sprite' || 'forgeax::sprite-lit'";
|
|
174
|
+
super(
|
|
175
|
+
`SpriteInstances: entity ${entityId} requires a sprite-shaded MaterialAsset.\n` +
|
|
176
|
+
` code: sprite-instances-requires-sprite-shader\n` +
|
|
177
|
+
` entityId: ${entityId}\n` +
|
|
178
|
+
` observedMaterialShaderId: ${observedMaterialShaderId}\n` +
|
|
179
|
+
` expected: ${expected}\n` +
|
|
180
|
+
` hint: ${hint}`,
|
|
181
|
+
);
|
|
182
|
+
this.hint = hint;
|
|
183
|
+
this.expected = expected;
|
|
184
|
+
this.detail = {
|
|
185
|
+
code: 'sprite-instances-requires-sprite-shader',
|
|
186
|
+
entityId,
|
|
187
|
+
observedMaterialShaderId,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Thrown / returned via Layer-3 error route when the same entity carries both
|
|
194
|
+
* `Instances` (3D per-instance mat4) and `SpriteInstances` (2D per-instance
|
|
195
|
+
* mat4 + UV region). The two primitives are peers — pick one. Detected at
|
|
196
|
+
* render-system-extract entry (M3 w13).
|
|
197
|
+
*
|
|
198
|
+
* `.code = 'sprite-instances-mutually-exclusive-with-instances'`
|
|
199
|
+
* `.detail = { entityId }`
|
|
200
|
+
* `.hint` — instructs the AI user to remove one of the two components.
|
|
201
|
+
*/
|
|
202
|
+
export class SpriteInstancesMutuallyExclusiveWithInstancesError extends Error {
|
|
203
|
+
override readonly name = 'SpriteInstancesMutuallyExclusiveWithInstancesError';
|
|
204
|
+
readonly code = 'sprite-instances-mutually-exclusive-with-instances' as const;
|
|
205
|
+
readonly hint: string;
|
|
206
|
+
readonly expected: string;
|
|
207
|
+
readonly detail: {
|
|
208
|
+
readonly code: 'sprite-instances-mutually-exclusive-with-instances';
|
|
209
|
+
readonly entityId: number;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
constructor(entityId: number) {
|
|
213
|
+
const hint =
|
|
214
|
+
'remove Instances or replace with SpriteInstances; SpriteInstances supersedes ' +
|
|
215
|
+
'Instances when per-instance region is needed.';
|
|
216
|
+
const expected = 'entity carries Instances XOR SpriteInstances (not both)';
|
|
217
|
+
super(
|
|
218
|
+
`SpriteInstances: entity ${entityId} carries both Instances and SpriteInstances.\n` +
|
|
219
|
+
` code: sprite-instances-mutually-exclusive-with-instances\n` +
|
|
220
|
+
` entityId: ${entityId}\n` +
|
|
221
|
+
` expected: ${expected}\n` +
|
|
222
|
+
` hint: ${hint}`,
|
|
223
|
+
);
|
|
224
|
+
this.hint = hint;
|
|
225
|
+
this.expected = expected;
|
|
226
|
+
this.detail = {
|
|
227
|
+
code: 'sprite-instances-mutually-exclusive-with-instances',
|
|
228
|
+
entityId,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import type { Component, ComponentSchema, FieldReflection } from '../component';
|
|
2
|
+
import { componentDefinition } from '../component-schema';
|
|
3
|
+
|
|
4
|
+
export class TimeDeltaInvalidError extends Error {
|
|
5
|
+
override readonly name = 'TimeDeltaInvalidError';
|
|
6
|
+
readonly code = 'time-delta-invalid' as const;
|
|
7
|
+
readonly expected = 'a finite delta greater than or equal to 0';
|
|
8
|
+
readonly hint = 'Call world.update(deltaSeconds) with a finite non-negative delta.';
|
|
9
|
+
readonly detail: { readonly received: number };
|
|
10
|
+
|
|
11
|
+
constructor(received: number) {
|
|
12
|
+
super(
|
|
13
|
+
`Invalid world.update delta: ${received}.\n expected: a finite delta greater than or equal to 0\n hint: Call world.update(deltaSeconds) with a finite non-negative delta.`,
|
|
14
|
+
);
|
|
15
|
+
this.detail = { received };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class TimeConfigInvalidError extends Error {
|
|
20
|
+
override readonly name = 'TimeConfigInvalidError';
|
|
21
|
+
readonly code = 'time-config-invalid' as const;
|
|
22
|
+
readonly expected: string;
|
|
23
|
+
readonly hint = 'Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.';
|
|
24
|
+
readonly detail: {
|
|
25
|
+
readonly fixedDeltaSeconds: number;
|
|
26
|
+
readonly maxStepsPerUpdate: number;
|
|
27
|
+
readonly maxDeltaSeconds: number;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
constructor(detail: TimeConfigInvalidError['detail']) {
|
|
31
|
+
const expected = 'maxDeltaSeconds >= (maxStepsPerUpdate + 1) * fixedDeltaSeconds';
|
|
32
|
+
super(
|
|
33
|
+
`Invalid World time policy.\n expected: ${expected}\n hint: Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.`,
|
|
34
|
+
);
|
|
35
|
+
this.expected = expected;
|
|
36
|
+
this.detail = detail;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class ScheduleScopeMismatchError extends Error {
|
|
41
|
+
override readonly name = 'ScheduleScopeMismatchError';
|
|
42
|
+
readonly code = 'schedule-scope-mismatch' as const;
|
|
43
|
+
readonly expected: string;
|
|
44
|
+
readonly hint: string;
|
|
45
|
+
readonly detail: {
|
|
46
|
+
readonly sourceSchedule: string;
|
|
47
|
+
readonly targetSchedule: string;
|
|
48
|
+
readonly reference?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
constructor(sourceSchedule: string, targetSchedule: string, reference?: string) {
|
|
52
|
+
const expected = `a reference owned by ${sourceSchedule}`;
|
|
53
|
+
const hint = `The referenced item belongs to ${targetSchedule}; register and order it in ${sourceSchedule}.`;
|
|
54
|
+
super(`Schedule scope mismatch.\n expected: ${expected}\n hint: ${hint}`);
|
|
55
|
+
this.expected = expected;
|
|
56
|
+
this.hint = hint;
|
|
57
|
+
this.detail = { sourceSchedule, targetSchedule, ...(reference ? { reference } : {}) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returned when a closed enum field receives a value outside its reflected
|
|
63
|
+
* labels. The write owner runs this before any archetype or column mutation.
|
|
64
|
+
*/
|
|
65
|
+
export class ComponentFieldInvalidValueError extends Error {
|
|
66
|
+
override readonly name = 'ComponentFieldInvalidValueError';
|
|
67
|
+
readonly code = 'component-field-invalid-value' as const;
|
|
68
|
+
readonly hint: string;
|
|
69
|
+
readonly expected: string;
|
|
70
|
+
readonly detail: {
|
|
71
|
+
readonly entity: number | undefined;
|
|
72
|
+
readonly component: string;
|
|
73
|
+
readonly field: string;
|
|
74
|
+
readonly received: unknown;
|
|
75
|
+
readonly allowedValues: Readonly<Record<string, number>>;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
constructor(
|
|
79
|
+
entity: number | undefined,
|
|
80
|
+
component: string,
|
|
81
|
+
field: string,
|
|
82
|
+
received: unknown,
|
|
83
|
+
allowedValues: Readonly<Record<string, number>>,
|
|
84
|
+
) {
|
|
85
|
+
const entries = Object.entries(allowedValues)
|
|
86
|
+
.map(([label, value]) => `${label}=${value}`)
|
|
87
|
+
.join(', ');
|
|
88
|
+
const expected = `${component}.${field} in { ${entries} }`;
|
|
89
|
+
const hint = `Set ${component}.${field} to one of the reflected enum values: ${entries}`;
|
|
90
|
+
super(
|
|
91
|
+
`${component}.${field} received an invalid enum value.\n` +
|
|
92
|
+
` code: component-field-invalid-value\n` +
|
|
93
|
+
` component: ${component}\n` +
|
|
94
|
+
` field: ${field}\n` +
|
|
95
|
+
` received: ${String(received)}\n` +
|
|
96
|
+
` expected: ${expected}\n` +
|
|
97
|
+
` hint: ${hint}`,
|
|
98
|
+
);
|
|
99
|
+
this.hint = hint;
|
|
100
|
+
this.expected = expected;
|
|
101
|
+
this.detail = { entity, component, field, received, allowedValues };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Returned before an ECS write when a managed `array<T>` field receives a
|
|
107
|
+
* value that the storage boundary cannot interpret as an array payload. The
|
|
108
|
+
* old column writer treated arbitrary objects as an empty payload, which
|
|
109
|
+
* silently changed the row while retaining no evidence of the caller error.
|
|
110
|
+
*/
|
|
111
|
+
export class ManagedArrayInvalidValueError extends Error {
|
|
112
|
+
override readonly name = 'ManagedArrayInvalidValueError';
|
|
113
|
+
readonly code = 'managed-array-invalid-value' as const;
|
|
114
|
+
readonly expected = 'an Array or TypedArray payload (or null/undefined to clear it)';
|
|
115
|
+
readonly hint: string;
|
|
116
|
+
readonly detail: {
|
|
117
|
+
readonly component: string;
|
|
118
|
+
readonly field: string;
|
|
119
|
+
readonly fieldType: string;
|
|
120
|
+
readonly actualValue: unknown;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
constructor(componentName: string, fieldName: string, fieldType: string, actualValue: unknown) {
|
|
124
|
+
const hint =
|
|
125
|
+
`Set ${componentName}.${fieldName} to a plain array or TypedArray matching ` +
|
|
126
|
+
`${fieldType}; use null or undefined to clear the managed value.`;
|
|
127
|
+
super(
|
|
128
|
+
`${componentName}.${fieldName}: managed array received an invalid value.\n` +
|
|
129
|
+
` code: managed-array-invalid-value\n` +
|
|
130
|
+
` fieldType: ${fieldType}\n` +
|
|
131
|
+
` expected: an Array or TypedArray payload (or null/undefined to clear it)\n` +
|
|
132
|
+
` hint: ${hint}`,
|
|
133
|
+
);
|
|
134
|
+
this.hint = hint;
|
|
135
|
+
this.detail = { component: componentName, field: fieldName, fieldType, actualValue };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Validate the closed enum fields present in a write payload. Enums without
|
|
141
|
+
* labels remain open numeric fields for compatibility with existing schemas.
|
|
142
|
+
*/
|
|
143
|
+
export function validateEnumFieldValues<S extends ComponentSchema>(
|
|
144
|
+
component: Component<string, S>,
|
|
145
|
+
raw: Partial<Record<string, unknown>> | undefined,
|
|
146
|
+
entity?: number,
|
|
147
|
+
): ComponentFieldInvalidValueError | null {
|
|
148
|
+
if (raw === undefined) return null;
|
|
149
|
+
const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;
|
|
150
|
+
const rawValues = raw as Record<string, unknown>;
|
|
151
|
+
for (const fieldName of Object.keys(rawValues)) {
|
|
152
|
+
const reflection = fields[fieldName];
|
|
153
|
+
if (reflection?.type !== 'enum' || reflection.labels === undefined) continue;
|
|
154
|
+
const value = rawValues[fieldName];
|
|
155
|
+
const allowedValues = Object.values(reflection.labels);
|
|
156
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || !allowedValues.includes(value)) {
|
|
157
|
+
return new ComponentFieldInvalidValueError(
|
|
158
|
+
entity,
|
|
159
|
+
component.name,
|
|
160
|
+
fieldName,
|
|
161
|
+
value,
|
|
162
|
+
reflection.labels,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
170
|
+
// feat-20260519-light-casters-point-spot-pbr w2 — closed-union evolution +1.
|
|
171
|
+
//
|
|
172
|
+
// Adds 1 new member 'spawn-light-invalid-bounds' to EcsErrorCode (23 -> 24).
|
|
173
|
+
// AGENTS.md section Error model evolution contract: minor (add member only).
|
|
174
|
+
// Triggered by PointLight / SpotLight spawn-time payload validation
|
|
175
|
+
// (plan-strategy D-S3 a). detail.field three-branch
|
|
176
|
+
// ('range' | 'innerOuter' | 'outerNinety') keeps the four bound-violation
|
|
177
|
+
// shapes under one error code so callers narrow first on `.code` then on
|
|
178
|
+
// `.detail.field` (charter P3 progressive disclosure).
|
|
179
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Returned via `Result.err` from `world.spawn` when a PointLight or SpotLight
|
|
183
|
+
* payload field is out of the documented bound. Four bound violations share
|
|
184
|
+
* one `.code` and discriminate via `.detail.field`:
|
|
185
|
+
*
|
|
186
|
+
* - `range` — PointLight / SpotLight `range < 0` or `Number.isNaN(range)`.
|
|
187
|
+
* Use `Number.POSITIVE_INFINITY` for an unlimited range or a non-negative
|
|
188
|
+
* meter value.
|
|
189
|
+
* - `innerOuter` — SpotLight `outerConeDeg <= innerConeDeg`. Inner cone is
|
|
190
|
+
* the saturated bright region; outer cone is the falloff edge.
|
|
191
|
+
* - `outerNinety` — SpotLight `outerConeDeg > 90`. KHR_lights_punctual upper
|
|
192
|
+
* bound. A spot light cone wider than 90 degrees becomes a point light;
|
|
193
|
+
* use PointLight instead.
|
|
194
|
+
* - `direction` — DirectionalLight / SpotLight `direction` is missing or a
|
|
195
|
+
* zero vector `[0, 0, 0]`. Direction has no default (there is no universal
|
|
196
|
+
* default direction): omitting it lands the array layer-3 all-zero, which is
|
|
197
|
+
* the same illegal state as an explicit zero vector. Supply a non-zero
|
|
198
|
+
* direction (feat-20260709 M2 / D-1, add-only union member).
|
|
199
|
+
*
|
|
200
|
+
* `.code = 'spawn-light-invalid-bounds'`
|
|
201
|
+
* `.detail.field` is derived from `keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY`;
|
|
202
|
+
* `.detail.got` is `number | readonly number[]`.
|
|
203
|
+
* `.hint` — names the offending field plus the valid replacement form.
|
|
204
|
+
*/
|
|
205
|
+
const SPAWN_LIGHT_INVALID_BOUNDS_POLICY = {
|
|
206
|
+
range: {
|
|
207
|
+
expected: 'range >= 0 or Number.POSITIVE_INFINITY',
|
|
208
|
+
hint: (componentName: string, got: number | readonly number[]) =>
|
|
209
|
+
`${componentName}.range = ${got} is invalid; use Number.POSITIVE_INFINITY for unlimited range, or a non-negative meter value`,
|
|
210
|
+
},
|
|
211
|
+
innerOuter: {
|
|
212
|
+
expected: 'outerConeDeg > innerConeDeg',
|
|
213
|
+
hint: (componentName: string, got: number | readonly number[]) =>
|
|
214
|
+
`${componentName}.outerConeDeg <= innerConeDeg (got ${got}); inner cone is the saturated bright region, outer cone is the falloff edge; outerConeDeg > innerConeDeg required`,
|
|
215
|
+
},
|
|
216
|
+
outerNinety: {
|
|
217
|
+
expected: 'outerConeDeg <= 90 (KHR_lights_punctual upper bound)',
|
|
218
|
+
hint: (componentName: string, got: number | readonly number[]) =>
|
|
219
|
+
`${componentName}.outerConeDeg = ${got} > 90; a spot light cone wider than 90 degrees becomes a point light; use PointLight instead`,
|
|
220
|
+
},
|
|
221
|
+
direction: {
|
|
222
|
+
expected: 'direction is a non-zero [x, y, z] vector',
|
|
223
|
+
hint: (componentName: string, got: number | readonly number[]) =>
|
|
224
|
+
`${componentName}.direction is missing or a zero vector (got ${JSON.stringify(got)}); direction has no default, provide a non-zero direction, e.g. [-0.5, -1, -0.3]`,
|
|
225
|
+
},
|
|
226
|
+
} satisfies Record<
|
|
227
|
+
string,
|
|
228
|
+
{
|
|
229
|
+
readonly expected: string;
|
|
230
|
+
readonly hint: (componentName: string, got: number | readonly number[]) => string;
|
|
231
|
+
}
|
|
232
|
+
>;
|
|
233
|
+
|
|
234
|
+
export class SpawnLightInvalidBoundsError extends Error {
|
|
235
|
+
override readonly name = 'SpawnLightInvalidBoundsError';
|
|
236
|
+
readonly code = 'spawn-light-invalid-bounds' as const;
|
|
237
|
+
readonly hint: string;
|
|
238
|
+
readonly expected: string;
|
|
239
|
+
readonly detail: {
|
|
240
|
+
readonly field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY;
|
|
241
|
+
readonly got: number | readonly number[];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
constructor(
|
|
245
|
+
componentName: string,
|
|
246
|
+
field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY,
|
|
247
|
+
got: number | readonly number[],
|
|
248
|
+
) {
|
|
249
|
+
const policy = SPAWN_LIGHT_INVALID_BOUNDS_POLICY[field];
|
|
250
|
+
const hint = policy.hint(componentName, got);
|
|
251
|
+
const expectedStr = policy.expected;
|
|
252
|
+
super(
|
|
253
|
+
`${componentName}: spawn payload bound violation.\n` +
|
|
254
|
+
` code: spawn-light-invalid-bounds\n` +
|
|
255
|
+
` component: ${componentName}\n` +
|
|
256
|
+
` field: ${field}\n` +
|
|
257
|
+
` got: ${got}\n` +
|
|
258
|
+
` expected: ${expectedStr}\n` +
|
|
259
|
+
` hint: ${hint}`,
|
|
260
|
+
);
|
|
261
|
+
this.hint = hint;
|
|
262
|
+
this.expected = expectedStr;
|
|
263
|
+
this.detail = { field, got };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returned via `Result.err` from resource-setter helpers (e.g.
|
|
269
|
+
* `setTransparentSortConfig`) when a numeric payload field violates the
|
|
270
|
+
* closed bound declared by the resource contract. The first consumer is
|
|
271
|
+
* `TransparentSortConfig.mode ∈ {0, 1, 2}` (plan-strategy D-4); future
|
|
272
|
+
* resource validators with the same shape reuse this code by routing
|
|
273
|
+
* through `.detail.receivedKey` to disambiguate which resource validator
|
|
274
|
+
* surfaced the failure.
|
|
275
|
+
*
|
|
276
|
+
* Closed-set kebab code consistent with `spawn-light-invalid-bounds`
|
|
277
|
+
* (feat-20260519 / w2); AI users consume via `switch (err.code)` exhaustive
|
|
278
|
+
* narrows + `err.detail.receivedMode` (or `err.detail.receivedKey` /
|
|
279
|
+
* `err.expected`) property access — never string-parse the message.
|
|
280
|
+
*
|
|
281
|
+
* `.code = 'resource-invalid-value'`
|
|
282
|
+
* `.detail = { receivedMode: number; receivedKey?: string }`
|
|
283
|
+
* `.hint` — direct copy-paste recovery (e.g. "0=layer-z, 1=layer-y,
|
|
284
|
+
* 2=layer-yz" for the sort-config case).
|
|
285
|
+
* `.expected` — the bound contract literal (e.g. "mode ∈ {0, 1, 2}").
|
|
286
|
+
*
|
|
287
|
+
* @reuses RhiError structured shape — same `.code / .expected / .hint /
|
|
288
|
+
* .detail` quadruple AI users consume across rhi + ecs.
|
|
289
|
+
*/
|
|
290
|
+
export class ResourceInvalidValueError extends Error {
|
|
291
|
+
override readonly name = 'ResourceInvalidValueError';
|
|
292
|
+
readonly code = 'resource-invalid-value' as const;
|
|
293
|
+
readonly hint: string;
|
|
294
|
+
readonly expected: string;
|
|
295
|
+
readonly detail: { readonly receivedMode: number; readonly receivedKey?: string };
|
|
296
|
+
|
|
297
|
+
constructor(
|
|
298
|
+
expected: string,
|
|
299
|
+
hint: string,
|
|
300
|
+
detail: { readonly receivedMode: number; readonly receivedKey?: string },
|
|
301
|
+
) {
|
|
302
|
+
const keyClause = detail.receivedKey === undefined ? '' : ` key: ${detail.receivedKey}\n`;
|
|
303
|
+
super(
|
|
304
|
+
`resource: invalid value.\n` +
|
|
305
|
+
` code: resource-invalid-value\n` +
|
|
306
|
+
keyClause +
|
|
307
|
+
` receivedMode: ${detail.receivedMode}\n` +
|
|
308
|
+
` expected: ${expected}\n` +
|
|
309
|
+
` hint: ${hint}`,
|
|
310
|
+
);
|
|
311
|
+
this.hint = hint;
|
|
312
|
+
this.expected = expected;
|
|
313
|
+
this.detail = detail;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
// feat-20260521-sprite-atlas-animation M1 T-05 — closed-union evolution +1.
|
|
319
|
+
//
|
|
320
|
+
// Adds 1 new member 'sprite-animation-invalid' to EcsErrorCode (25 -> 26).
|
|
321
|
+
// AGENTS.md §Error model evolution contract: minor (add member only).
|
|
322
|
+
// Same-shape add-only mirror of SpawnLightInvalidBoundsError (feat-20260519
|
|
323
|
+
// w2 line 736-776) and ResourceInvalidValueError (feat-20260520 w13 line
|
|
324
|
+
// 862) — the kebab `'<noun>-invalid-...'` series keeps `switch (err.code)`
|
|
325
|
+
// exhaustive narrows visually consistent (charter P4 consistent abstraction;
|
|
326
|
+
// research F-7 candidate A).
|
|
327
|
+
//
|
|
328
|
+
// Triggered by `spriteAnimationTickSystem` (packages/runtime/src/systems/
|
|
329
|
+
// sprite-animation-tick.ts, landed in M4 T-23) when an entity's
|
|
330
|
+
// `SpriteAnimation` row violates one of two runtime invariants:
|
|
331
|
+
//
|
|
332
|
+
// - field='regions-length' -> `regions.length !== frameCount * 4`
|
|
333
|
+
// - field='frame-duration' -> `frameDuration <= 0`
|
|
334
|
+
//
|
|
335
|
+
// `.detail.field` two-branch (charter P3: AI users branch once on
|
|
336
|
+
// `err.code` and once on `err.detail.field` to reach the recovery hint
|
|
337
|
+
// without parsing the message). Plan-strategy section 2 D-1 binds the
|
|
338
|
+
// detail field shape; M4 T-19 / T-20 / T-21 cover the runtime fail-fast
|
|
339
|
+
// paths end-to-end.
|
|
340
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Returned via `Result.err` from `spriteAnimationTickSystem` (M4 T-23) when
|
|
344
|
+
* an entity's `SpriteAnimation` row violates a runtime invariant.
|
|
345
|
+
* Two invariants share one `.code` and discriminate via `.detail.field`:
|
|
346
|
+
*
|
|
347
|
+
* - `regions-length` — `SpriteAnimation.regions.length !== frameCount * 4`.
|
|
348
|
+
* `regions` packs `[uMin, vMin, uW, vH]` per frame so the length must be
|
|
349
|
+
* exactly `frameCount * 4`. Detail carries the offending `regionsLength`
|
|
350
|
+
* alongside the declared `frameCount` so the hint can spell the exact
|
|
351
|
+
* delta in callsite-friendly numbers.
|
|
352
|
+
* - `frame-duration` — `SpriteAnimation.frameDuration <= 0` (covers both
|
|
353
|
+
* `frameDuration === 0` and `frameDuration < 0`; T-21 binds the negative
|
|
354
|
+
* case to the same arm so AI users handle both via a single
|
|
355
|
+
* `if (err.detail.field === 'frame-duration')` branch — charter P4
|
|
356
|
+
* consistent abstraction).
|
|
357
|
+
*
|
|
358
|
+
* `.code = 'sprite-animation-invalid'`
|
|
359
|
+
* `.detail = { field: 'regions-length', regionsLength, frameCount } |
|
|
360
|
+
* { field: 'frame-duration', frameDuration }`
|
|
361
|
+
*
|
|
362
|
+
* Two top-level detail variants give each `.field` branch its own
|
|
363
|
+
* required sub-field shape so AI users get strong narrowing inside
|
|
364
|
+
* `switch (err.detail.field)` without optional sub-fields bleeding
|
|
365
|
+
* across branches (mirrors `SpawnLightInvalidBoundsError`'s shared
|
|
366
|
+
* `got: number` shape but adapted because regions-length /
|
|
367
|
+
* frame-duration carry different sub-field counts).
|
|
368
|
+
*
|
|
369
|
+
* `.hint` — names the offending invariant plus the valid replacement form.
|
|
370
|
+
*/
|
|
371
|
+
export class SpriteAnimationInvalidError extends Error {
|
|
372
|
+
override readonly name = 'SpriteAnimationInvalidError';
|
|
373
|
+
readonly code = 'sprite-animation-invalid' as const;
|
|
374
|
+
readonly hint: string;
|
|
375
|
+
readonly expected: string;
|
|
376
|
+
readonly detail:
|
|
377
|
+
| {
|
|
378
|
+
readonly field: 'regions-length';
|
|
379
|
+
readonly regionsLength: number;
|
|
380
|
+
readonly frameCount: number;
|
|
381
|
+
}
|
|
382
|
+
| {
|
|
383
|
+
readonly field: 'frame-duration';
|
|
384
|
+
readonly frameDuration: number;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
private static resolvePolicy(detail: SpriteAnimationInvalidError['detail']): {
|
|
388
|
+
readonly expected: string;
|
|
389
|
+
readonly hint: string;
|
|
390
|
+
} {
|
|
391
|
+
switch (detail.field) {
|
|
392
|
+
case 'regions-length':
|
|
393
|
+
return {
|
|
394
|
+
expected: 'SpriteAnimation.regions.length === frameCount * 4',
|
|
395
|
+
hint: `SpriteAnimation.regions.length = ${detail.regionsLength} does not match frameCount * 4 = ${detail.frameCount * 4}; pack 4 floats [uMin, vMin, uW, vH] per frame (see <name>.atlas.meta.json sidecar 'regions' map)`,
|
|
396
|
+
};
|
|
397
|
+
case 'frame-duration':
|
|
398
|
+
return {
|
|
399
|
+
expected: 'SpriteAnimation.frameDuration > 0',
|
|
400
|
+
hint: `SpriteAnimation.frameDuration = ${detail.frameDuration} is invalid; use a positive seconds-per-frame value (e.g. 0.1 = 10 fps)`,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
constructor(detail: SpriteAnimationInvalidError['detail']) {
|
|
406
|
+
const policy = SpriteAnimationInvalidError.resolvePolicy(detail);
|
|
407
|
+
super(
|
|
408
|
+
`SpriteAnimation: invariant violated.\n` +
|
|
409
|
+
` code: sprite-animation-invalid\n` +
|
|
410
|
+
` field: ${detail.field}\n` +
|
|
411
|
+
` expected: ${policy.expected}\n` +
|
|
412
|
+
` hint: ${policy.hint}`,
|
|
413
|
+
);
|
|
414
|
+
this.hint = policy.hint;
|
|
415
|
+
this.expected = policy.expected;
|
|
416
|
+
this.detail = detail;
|
|
417
|
+
}
|
|
418
|
+
}
|