@forgeax/engine-ecs 0.1.26 → 0.1.27

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.
Files changed (50) hide show
  1. package/README.md +94 -35
  2. package/dist/__tests__/world-read.unit.test.d.ts +2 -0
  3. package/dist/__tests__/world-read.unit.test.d.ts.map +1 -0
  4. package/dist/commands.d.ts +2 -0
  5. package/dist/commands.d.ts.map +1 -1
  6. package/dist/index.mjs +3471 -4263
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/internal.d.ts +2 -3
  9. package/dist/internal.d.ts.map +1 -1
  10. package/dist/internal.mjs +3 -333
  11. package/dist/internal.mjs.map +1 -1
  12. package/dist/projection/index.mjs.map +1 -1
  13. package/dist/shared.mjs.map +1 -1
  14. package/dist/world-entity-lifecycle.d.ts +3 -14
  15. package/dist/world-entity-lifecycle.d.ts.map +1 -1
  16. package/dist/world-internal.d.ts +65 -5
  17. package/dist/world-internal.d.ts.map +1 -1
  18. package/dist/world-read.d.ts +16 -0
  19. package/dist/world-read.d.ts.map +1 -0
  20. package/dist/world-read.mjs +8 -0
  21. package/dist/world-read.mjs.map +1 -0
  22. package/dist/world-scheduling.d.ts +0 -4
  23. package/dist/world-scheduling.d.ts.map +1 -1
  24. package/dist/world-storage-primitives.d.ts +26 -0
  25. package/dist/world-storage-primitives.d.ts.map +1 -0
  26. package/dist/world.d.ts +352 -157
  27. package/dist/world.d.ts.map +1 -1
  28. package/package.json +8 -4
  29. package/src/__tests__/command-buffer.test.ts +29 -3
  30. package/src/__tests__/hierarchy.unit.test.ts +3 -3
  31. package/src/__tests__/world-health.contract.test.ts +195 -2
  32. package/src/__tests__/world-read.unit.test.ts +30 -0
  33. package/src/commands.ts +22 -9
  34. package/src/internal.ts +5 -3
  35. package/src/world-entity-lifecycle.ts +23 -252
  36. package/src/world-internal-augmentation.d.ts +11 -0
  37. package/src/world-internal.ts +114 -63
  38. package/src/world-read.ts +38 -0
  39. package/src/world-scheduling.ts +0 -26
  40. package/src/world-storage-primitives.ts +179 -0
  41. package/src/world.ts +2590 -509
  42. package/dist/world-component-access.d.ts +0 -311
  43. package/dist/world-component-access.d.ts.map +0 -1
  44. package/dist/world-component-storage.d.ts +0 -298
  45. package/dist/world-component-storage.d.ts.map +0 -1
  46. package/dist/world-core.d.ts +0 -39
  47. package/dist/world-core.d.ts.map +0 -1
  48. package/src/world-component-access.ts +0 -1769
  49. package/src/world-component-storage.ts +0 -1264
  50. package/src/world-core.ts +0 -74
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/component-schema.ts","../src/world-internal.ts","../src/errors.ts","../src/component.ts","../src/internal.ts"],"names":[],"mappings":"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","file":"internal.mjs","sourcesContent":["import type { Component, FieldReflection, SchemaFieldType } from './component';\n\n/** The complete storage placement vocabulary owned by ECS schema. */\nexport type ComponentStorageKind = 'table' | 'sparse';\n\n/** The stable public facts needed to describe a component schema offline. */\nexport interface ComponentSchemaDefinition {\n readonly name: string;\n readonly fields: Readonly<Record<string, SchemaFieldType>>;\n readonly storage: ComponentStorageKind;\n}\n\n/**\n * Serialization facts owned beside schema registration, not carried by the\n * public component token. Queries and storage only need the token's schema\n * vocabulary; domain validation and lifecycle belong to their owners.\n */\nexport interface ComponentPolicy {\n readonly transient: boolean;\n readonly meta: Record<string, unknown>;\n /** Components that are materialized whenever this component is added. */\n readonly requires: readonly Component[];\n}\n\n/** Immutable schema reflection plus non-token policy projection. */\nexport interface ComponentDefinition {\n readonly fields: Readonly<Record<string, FieldReflection>>;\n readonly defaults: Readonly<Record<string, unknown>> | undefined;\n readonly policy: ComponentPolicy;\n}\n\n// Component tokens can cross independently bundled ECS entry points. Share\n// the owner registry through globalThis rather than attaching a hidden symbol\n// to each token; `Reflect.ownKeys(token)` must remain exactly the three public\n// facts name/fields/storage.\nconst COMPONENT_REGISTRY = Symbol.for('forgeax.ecs.componentRegistry');\ninterface ComponentRegistry {\n readonly definitions: WeakMap<object, ComponentDefinition>;\n}\nconst globalSymbols = globalThis as typeof globalThis & { [key: symbol]: unknown };\nconst definitions =\n (globalSymbols[COMPONENT_REGISTRY] as ComponentRegistry | undefined) ??\n (() => {\n const registry: ComponentRegistry = { definitions: new WeakMap<object, ComponentDefinition>() };\n globalSymbols[COMPONENT_REGISTRY] = registry;\n return registry;\n })();\n\nexport function registerComponentDefinition(\n component: Component,\n definition: ComponentDefinition,\n): void {\n definitions.definitions.set(component, definition);\n}\n\n/** Read the definition-time schema/policy projection for a component token. */\nexport function componentDefinition(component: Component): ComponentDefinition {\n const definition = definitions.definitions.get(component);\n if (definition === undefined) {\n throw new Error(`Component definition missing for '${component.name}'.`);\n }\n return definition;\n}\n\n/** Read the generic structural requirements declared by one component. */\nexport function componentRequirements(component: Component): readonly Component[] {\n // Keep invalid/foreign tokens on the ordinary preflight error path. The\n // expansion helper runs before validation, so it must not turn a structured\n // `component-not-defined` Result into an uncaught registry exception.\n return definitions.definitions.get(component)?.policy.requires ?? [];\n}\n\ntype ComponentDataLike = {\n readonly component: Component;\n readonly data: Partial<Record<string, unknown>>;\n};\n\n/**\n * Expand component requirements once at the structural boundary.\n *\n * Explicit component data wins and is never duplicated. Requirements are\n * appended in declaration order, and the same identity set also terminates a\n * malformed dependency cycle without a per-frame scan.\n */\nexport function expandComponentRequirements<T extends ComponentDataLike>(\n componentDatas: readonly T[],\n): T[] {\n let hasRequirements = false;\n for (const entry of componentDatas) {\n if (componentRequirements(entry.component).length !== 0) {\n hasRequirements = true;\n break;\n }\n }\n // Most structural operations use components without dependencies. Preserve\n // that path without copying or allocating a Set; callers only consume the\n // returned list and never mutate it.\n if (!hasRequirements) return componentDatas as T[];\n\n const expanded = [...componentDatas];\n const seen = new Set<Component>(expanded.map((entry) => entry.component));\n for (let index = 0; index < expanded.length; index++) {\n const component = expanded[index]?.component;\n if (component === undefined) continue;\n for (const required of componentRequirements(component)) {\n if (seen.has(required)) continue;\n seen.add(required);\n expanded.push({ component: required, data: {} } as T);\n }\n }\n return expanded;\n}\n\n/**\n * Freeze a schema value recursively. Component descriptors are authored data,\n * so nested defaults and enum label maps must not become mutation channels.\n */\nexport function deepFreeze<T>(value: T): Readonly<T> {\n if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {\n return value as Readonly<T>;\n }\n // Non-empty TypedArrays reject Object.freeze and their indexed elements\n // remain writable even when the wrapper is frozen. Treat binary views as\n // immutable leaf projections; the authored descriptor around them is still\n // recursively frozen by this function.\n if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {\n return value as Readonly<T>;\n }\n const object = value as object;\n for (const key of Reflect.ownKeys(object)) {\n const child = (object as Record<PropertyKey, unknown>)[key];\n if (child !== null && (typeof child === 'object' || typeof child === 'function')) {\n deepFreeze(child);\n }\n }\n return Object.freeze(value) as Readonly<T>;\n}\n\nexport function assertComponentStorage(value: string): asserts value is ComponentStorageKind {\n if (value !== 'table' && value !== 'sparse') {\n throw new Error(`Unsupported component storage '${value}'. Expected 'table' or 'sparse'.`);\n }\n}\n","/**\n * ECS package-internal World seam.\n *\n * This module is intentionally not re-exported by the package entry points.\n * It keeps implementation access out of World's discoverable API while\n * retaining direct bound calls for the hot query and structural paths.\n */\n// Bundled ECS entry points (`index` and `projection`) each include this module\n// in their own closure. A plain Symbol() therefore gives World and projection\n// different property keys at runtime even though their source imports agree.\n// The registry is package-private by convention: no root/advanced export\n// exposes this key, while Symbol.for keeps source/dist and split bundles on\n// one identity.\nexport const worldInternal: unique symbol = Symbol.for(\n 'forgeax.ecs.worldInternal',\n) as unknown as typeof worldInternal;\n\ntype InternalName =\n | 'addComponentCore'\n | 'allocateIndex'\n | 'allocatePendingEntity'\n | 'cancelPendingEntity'\n | 'despawnCore'\n | 'getArrayView'\n | 'getArrayLength'\n | 'getArrayElement'\n | 'getFieldValue'\n | 'getBufferPool'\n | 'getClockWriter'\n | 'getComponentChange'\n | 'getComponentMutationEpoch'\n | 'getComponentMutationEpochs'\n | 'getEntityArchetype'\n | 'getFixedAccumulator'\n | 'getFreeIndices'\n | 'getGraph'\n | 'getMutationEpoch'\n | 'getQueryRow'\n | 'getRecords'\n | 'getRelationshipEpoch'\n | 'getRelationshipTargetEntities'\n | 'getResources'\n | 'getSchedule'\n | 'getSchedules'\n | 'getSharedRefs'\n | 'getStructureEpoch'\n | 'getStructuralEvidence'\n | 'getUniqueRefs'\n | 'lookupAlive'\n | 'markComponentAdded'\n | 'markComponentChanged'\n | 'markComponentRangeChanged'\n | 'markComponentsAdded'\n | 'markStructureChanged'\n | 'materializePendingEntity'\n | 'nextMutationEpoch'\n | 'poisonExecution'\n | 'publishDerivedRange'\n | 'prepareRelationshipInsert'\n | 'preflightComponentData'\n | 'readRow'\n | 'recordStructuralEvidence'\n | 'recordIsLive'\n | 'relationshipOnInsert'\n | 'relationshipOnRemove'\n | 'releaseManagedRefsOnRow'\n | 'releaseRelationshipPreparation'\n | 'removeComponentCore'\n | 'routeError'\n | 'restoreMutationEpoch'\n | 'setFixedAccumulator'\n | 'setQueryRow'\n | 'spawnCore'\n | 'writeEntitySelf'\n | 'writeRow';\n\nexport type WorldInternal = {\n // biome-ignore lint/suspicious/noExplicitAny: this closed package-internal seam preserves each method's existing inferred result type.\n readonly [K in InternalName]: (...args: any[]) => any;\n};\n","// @forgeax/engine-ecs — typed error class collection.\n//\n// Typed error classes covering all boundary conditions. Each follows progressive\n// disclosure format: one-line summary → context fields → hint fix suggestion.\n// Each exposes a `.hint` readonly property for programmatic extraction.\n\nimport type { QuerySpanUnavailableReason } from './errors/query-and-component-errors';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Re-exports from split error sub-files (w3-b — package cohesion split)\n// ────────────────────────────────────────────────────────────────────────────\n\nexport {\n ComponentNotDefinedError,\n QueryDataRequiresFieldsError,\n QueryDescriptorConflictError,\n QueryIterationActiveError,\n QueryIterationInvalidatedError,\n QuerySpanUnavailableError,\n type QuerySpanUnavailableReason,\n RemoveEssentialComponentError,\n SpawnDataUnknownFieldError,\n} from './errors/query-and-component-errors';\n\nexport {\n RelationshipDetachMismatchError,\n RelationshipMirrorComponentNotRegisteredError,\n RelationshipMirrorFieldTypeMismatchError,\n RelationshipSelfCycleError,\n RelationshipTargetReadonlyError,\n} from './errors/relationship-errors';\nexport {\n SharedFieldInvalidValueError,\n SpriteInstancesCountMismatchError,\n SpriteInstancesMutuallyExclusiveWithInstancesError,\n SpriteInstancesRequiresSpriteShaderError,\n} from './errors/sprite-and-shared-errors';\nexport {\n ComponentFieldInvalidValueError,\n ComponentNumericValueInvalidError,\n ManagedArrayInvalidValueError,\n ResourceInvalidValueError,\n ScheduleScopeMismatchError,\n SpawnLightInvalidBoundsError,\n SpriteAnimationInvalidError,\n TimeConfigInvalidError,\n TimeDeltaInvalidError,\n validateEnumFieldValues,\n validateNumericFieldValues,\n} from './errors/validation-errors';\n\nexport {\n SharedKernelEligibilityError,\n SharedKernelFailureError,\n WorldPoisonedError,\n} from './execution/shared-kernel';\n\n/**\n * Thrown when an attempt is made to encode an entity index that does not fit\n * in 24 bits (i.e. >= 2^24 = 16_777_216).\n *\n * `.code = 'entity-index-overflow'`\n * `.hint` — suggests reducing entity count or investigating leaks.\n */\nexport class EntityIndexOverflowError extends RangeError {\n override readonly name = 'EntityIndexOverflowError';\n readonly code = 'entity-index-overflow' as const;\n readonly hint: string;\n\n constructor(index: number) {\n const hint =\n 'Entity index exceeds 24-bit max (16777215). Reduce simultaneous entity count or investigate entity leaks.';\n super(\n `Entity index ${index} exceeds 24-bit max (16777215).\\n` +\n ` index: ${index}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n }\n}\n\n/**\n * Thrown when `defineComponent` is given a field type that is not in the\n * supported scalar field type set.\n *\n * `.code = 'schema-unsupported-field'`\n * `.hint` — lists all supported scalar field types.\n */\nexport class SchemaUnsupportedFieldError extends Error {\n override readonly name = 'SchemaUnsupportedFieldError';\n readonly code = 'schema-unsupported-field' as const;\n readonly hint: string;\n\n constructor(fieldName: string, fieldType: string) {\n let hint = 'Supported types: f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 / bool / enum / ref.';\n // feat-20260614 M1 / M5: explicit migration hints for the two\n // retired schema-vocab keyword families. Both renames preserve brand\n // and storage layout (u32 column); only the keyword + dispatch arm\n // changed. AI users hitting either literal land directly on the new\n // keyword instead of grepping for the rename note (charter F1\n // single-entry indexability).\n if (fieldType.startsWith('handle<') && fieldType.endsWith('>')) {\n const tag = fieldType.slice(7, -1);\n hint = `'handle<${tag}>' was removed in feat-20260614 M5; use 'shared<${tag}>' instead. The brand and storage layout are unchanged; only the keyword + write-barrier dispatch (SharedRefStore retain/release) is new.`;\n } else if (fieldType.startsWith('ref<') && fieldType.endsWith('>')) {\n const tag = fieldType.slice(4, -1);\n hint = `'ref<${tag}>' was renamed in feat-20260614 M1; use 'unique<${tag}>' instead. The brand and storage layout are unchanged; the dispatch still routes through UniqueRefStore (single-holder direct release).`;\n }\n super(\n `Schema field \"${fieldName}\" has unsupported type \"${fieldType}\".\\n` +\n ` field: ${fieldName}\\n` +\n ` type: ${fieldType}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n }\n}\n\nexport class SparseStorageRequiresTagError extends Error {\n override readonly name = 'SparseStorageRequiresTagError';\n readonly code = 'sparse-storage-requires-tag' as const;\n readonly expected = 'sparse components have an empty schema and no relationship metadata';\n readonly hint = 'remove all fields and relationship metadata, or use storage: table';\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n super(\n `Sparse component \"${componentName}\" must be a zero-field, non-relationship tag.\\n` +\n ` component: ${componentName}\\n` +\n ` hint: remove all fields and relationship metadata, or use storage: table`,\n );\n this.detail = { componentName };\n }\n}\n\n/**\n * Thrown (or returned via the `Result` err branch — `r.ok === false`, `r.error`)\n * when get/set/addComponent/removeComponent is called on an entity that has\n * been despawned (stale handle).\n *\n * `.code = 'stale-entity'`\n * `.hint` — includes operation name, expected/actual generation, and component name.\n * Enhanced fields: `.component`, `.operation`, `.expectedGeneration`, `.actualGeneration`.\n */\nexport class StaleEntityError extends Error {\n override readonly name = 'StaleEntityError';\n readonly code = 'stale-entity' as const;\n readonly hint: string;\n\n /** Component name involved in the operation (undefined when the operation does not target a specific component). */\n readonly component: string | undefined;\n /** The component-level operation that triggered this error (e.g. 'get' / 'set' / 'add' / 'remove'). Entity-level operations like `despawn` are not surfaced here — see `EntityHandle` lifecycle errors. */\n readonly operation: string | undefined;\n /** The generation the caller expected (from the entity handle). */\n readonly expectedGeneration: number | undefined;\n /**\n * The actual generation found in the entity pool. `-1` is the sentinel\n * value for entities never allocated (slot was never occupied), as opposed\n * to allocated-then-despawned entities which carry a real (incremented)\n * generation number.\n */\n readonly actualGeneration: number | undefined;\n\n constructor(\n entityId: number,\n index: number,\n generation: number,\n enhanced?: {\n component?: string;\n operation: string;\n expectedGeneration: number;\n actualGeneration: number;\n },\n ) {\n const hint = enhanced\n ? `Entity was despawned. Operation \"${enhanced.operation}\" on entity ${entityId}` +\n (enhanced.component ? ` (component: ${enhanced.component})` : '') +\n ` expected generation ${enhanced.expectedGeneration}, found ${enhanced.actualGeneration}.` +\n ' Check entity lifecycle before access.'\n : 'Entity was despawned. Check entity lifecycle before access.';\n super(\n `Operation on stale entity handle.\\n` +\n ` entity: ${entityId} (index=${index}, generation=${generation})\\n` +\n (enhanced ? ` operation: ${enhanced.operation}\\n` : '') +\n (enhanced?.component ? ` component: ${enhanced.component}\\n` : '') +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.component = enhanced?.component;\n this.operation = enhanced?.operation;\n this.expectedGeneration = enhanced?.expectedGeneration;\n this.actualGeneration = enhanced?.actualGeneration;\n }\n}\n\n/**\n * Returned via the `Result` err branch (`r.ok === false`, `r.error`) when\n * addComponent tries to add a component that the entity already possesses.\n *\n * `.code = 'component-already-present'`\n * `.hint` — suggests using `set()` to update values instead.\n */\nexport class ComponentAlreadyPresentError extends Error {\n override readonly name = 'ComponentAlreadyPresentError';\n readonly code = 'component-already-present' as const;\n readonly hint: string;\n\n constructor(entityId: number, componentName: string) {\n const hint = 'Entity already has this component. Use set() to update values.';\n super(\n `Entity ${entityId} already has component \"${componentName}\".\\n` +\n ` entity: ${entityId}\\n` +\n ` component: ${componentName}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n }\n}\n\n/**\n * Returned via the `Result` err branch (`r.ok === false`, `r.error`) when\n * removeComponent / set is called for a component the entity does not possess.\n *\n * `.code = 'component-not-present'`\n * `.hint` — suggests checking with query or inspect().\n */\nexport class ComponentNotPresentError extends Error {\n override readonly name = 'ComponentNotPresentError';\n readonly code = 'component-not-present' as const;\n readonly hint: string;\n\n constructor(entityId: number, componentName: string) {\n const hint = 'Entity does not have this component. Check with query or inspect().';\n super(\n `Entity ${entityId} does not have component \"${componentName}\".\\n` +\n ` entity: ${entityId}\\n` +\n ` component: ${componentName}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n }\n}\n\n/**\n * Thrown when DAG Schedule detects a cyclic dependency among systems.\n *\n * `.code = 'cyclic-dependency'`\n * `.hint` — includes the cycle path and suggests removing one constraint.\n * `.detail` — `{ code: 'cyclic-dependency'; cycle: readonly string[] }` contains the\n * structured cycle path for programmatic consumption.\n */\nexport class CyclicDependencyError extends Error {\n override readonly name = 'CyclicDependencyError';\n readonly code = 'cyclic-dependency' as const;\n readonly expected = 'the schedule dependency graph is acyclic';\n readonly hint: string;\n /** Structured cycle path — programmatic consumers read this, not the message. */\n readonly detail: { readonly code: 'cyclic-dependency'; readonly cycle: readonly string[] };\n\n constructor(cycle: readonly string[]) {\n const cycleStr = cycle.join(' -> ');\n const hint = `Cycle path: ${cycleStr}. Remove one ordering constraint to break the cycle.`;\n super(`DAG Schedule has a cyclic dependency.\\n cycle: ${cycleStr}\\n hint: ${hint}`);\n this.hint = hint;\n this.detail = { code: 'cyclic-dependency' as const, cycle };\n }\n}\n\n/**\n * Returned via `Result.err` from `world.addSystems`\n * when a SystemSet token fails local structural validation.\n *\n * The sole public invalid-token error type (D-2a). Covers all rejection\n * scenarios: plain-object cast, unregistered name, stale token after\n * overwrite, and cross-realm copies.\n *\n * `.code = 'system-set-not-registered'`\n * `.expected` — the name of the unregistered token.\n * `.hint` — suggests passing the current token to the owning World schedule.\n * `.detail` — `{ code, name, registered }` where `registered` is a deterministic snapshot\n * of the current World-local schedule keys.\n */\nexport class SystemSetNotRegisteredError extends Error {\n override readonly name = 'SystemSetNotRegisteredError';\n readonly code = 'system-set-not-registered' as const;\n /** The name carried by the rejected token. */\n readonly expected: string;\n readonly hint: string;\n /** Deterministic snapshot of the current World-local schedule for repair. */\n readonly detail: {\n readonly code: 'system-set-not-registered';\n readonly name: string;\n readonly registered: readonly string[];\n };\n\n constructor(name: string, registered: readonly string[]) {\n const hint =\n `SystemSet \"${name}\" is not valid for the current World schedule. ` +\n `Pass a non-empty SystemSet token owned by this World schedule.`;\n const message =\n `SystemSet \"${name}\" is not registered.\\n` +\n ` expected: ${name}\\n` +\n ` registered: [${registered.join(', ')}]\\n` +\n ` hint: ${hint}`;\n super(message);\n this.expected = name;\n this.hint = hint;\n this.detail = { code: 'system-set-not-registered' as const, name, registered };\n }\n}\n\n/**\n * Factory for {@link SystemSetNotRegisteredError}. Consumed by\n * {@link validateSystemSetTokens} (w4) and the two mutation entry points.\n */\nexport function systemSetNotRegistered(\n name: string,\n registered: readonly string[],\n): SystemSetNotRegisteredError {\n return new SystemSetNotRegisteredError(name, registered);\n}\n\n/**\n * Closed-set ScheduleMutationError code union (M2 — plan-strategy D-3).\n *\n * Schedule add-only API (`removeSystem` / `replaceSystem`) returns\n * `Result<void, ScheduleMutationError>` carrying one of these codes. The\n * `@forgeax/engine-remote` layer (M3) bridges these strings to JSON-RPC\n * `RemoteErrorCode` 1:1 — keeping ECS free of console / wire dependencies.\n *\n * - `system-before-unknown` — name argument does not match any registered system.\n * - `system-name-conflict` — reserved for the M3 inject path; surfaced from\n * schedule when callers inject a name that already exists.\n * - `cyclic-injection` — schedule build detected a cycle introduced by\n * the mutation; carries the cycle path in `.detail.cycle`.\n */\nexport type ScheduleMutationErrorCode = 'system-before-unknown';\n\nexport interface ScheduleMutationErrorDetail {\n readonly cycle?: readonly string[];\n readonly candidates?: readonly string[];\n}\n\n/**\n * Returned via `Result.err` from `world.removeSystem` / `world.replaceSystem`.\n *\n * `.code` is the closed-set string SSOT consumed by the M3 console bridge;\n * `.hint` carries an AI-friendly self-repair suggestion; `.detail` is the\n * discriminated payload (cycle path for `cyclic-injection`, candidate list\n * for `system-before-unknown`).\n */\nexport class ScheduleMutationError extends Error {\n override readonly name = 'ScheduleMutationError';\n readonly code: ScheduleMutationErrorCode;\n readonly hint: string;\n readonly detail: ScheduleMutationErrorDetail;\n\n constructor(\n code: ScheduleMutationErrorCode,\n message: string,\n hint: string,\n detail: ScheduleMutationErrorDetail = {},\n ) {\n super(`${message}\\n code: ${code}\\n hint: ${hint}`);\n this.code = code;\n this.hint = hint;\n this.detail = detail;\n }\n}\n\n/**\n * Thrown when getResource is called with a key that does not exist.\n *\n * `.code = 'resource-not-found'`\n * `.hint` — suggests using `world.insertResource()` first.\n */\nexport class ResourceNotFoundError extends Error {\n override readonly name = 'ResourceNotFoundError';\n readonly code = 'resource-not-found' as const;\n readonly hint: string;\n\n constructor(key: string) {\n const hint = `Resource \"${key}\" not found. Insert with world.insertResource() first.`;\n super(`Resource \"${key}\" not found.\\n` + ` key: ${key}\\n` + ` hint: ${hint}`);\n this.hint = hint;\n }\n}\n\nexport class ChangeEpochExhaustedError extends Error {\n override readonly name = 'ChangeEpochExhaustedError';\n readonly code = 'change-epoch-exhausted' as const;\n readonly expected = 'mutationEpoch < Number.MAX_SAFE_INTEGER';\n readonly hint = 'Rebuild the World before performing another mutation.';\n readonly detail: { readonly epoch: number };\n\n constructor(epoch: number) {\n super(`World mutation epoch is exhausted at ${epoch}.\\n hint: Rebuild the World.`);\n this.detail = { epoch };\n }\n}\n\nexport class DerivedRangeOutOfBoundsError extends Error {\n override readonly name = 'DerivedRangeOutOfBoundsError';\n readonly code = 'derived-range-out-of-bounds' as const;\n readonly expected = 'a non-negative span-relative range with start + count <= span.length';\n readonly hint =\n 'check start and count against the QuerySpan length, then retry without changing World state';\n readonly detail: { readonly start: number; readonly count: number; readonly spanLength: number };\n\n constructor(start: number, count: number, spanLength: number) {\n super(`Derived range [${start}, ${start + count}) exceeds QuerySpan length ${spanLength}.`);\n this.detail = { start, count, spanLength };\n }\n}\n\n/**\n * Thrown when insertResource/removeResource is called on a World-owned\n * protected resource (Time or FixedTime).\n *\n * `.code = 'resource-protected'`\n * `.hint` — suggests using `world.update(delta)` or reading the resource.\n * `.expected` — the resource name that was rejected.\n */\nexport class ProtectedResourceError extends Error {\n override readonly name = 'ProtectedResourceError';\n readonly code = 'resource-protected' as const;\n readonly hint: string;\n readonly expected: string;\n\n constructor(resourceName: string, operation: 'insert' | 'remove') {\n const hint =\n operation === 'insert'\n ? `\"${resourceName}\" is a World-owned protected resource. It is advanced by world.update(delta); read it via world.getResource(${resourceName}).`\n : `\"${resourceName}\" is a World-owned protected resource. It is owned by the World scheduler and cannot be removed.`;\n const expected = `a user-owned resource key (not ${resourceName})`;\n super(\n `Protected resource \"${resourceName}\" cannot be ${operation}ed.\\n` +\n ` code: resource-protected\\n` +\n ` resource: ${resourceName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// w5 — managed-* closed-union extension (M0).\n//\n// Four error classes covering the managed-* family:\n//\n// managed-* : UniqueRefStore + BufferPool runtime fail-fast.\n// Returned via Result.err from M1 / M2 storage paths.\n//\n// `.code` uses lowercase-kebab literals consistent with `ScheduleMutationErrorCode`\n// (the prior closed-set convention). The 9 legacy errors keep their\n// SCREAMING_SNAKE_CASE codes — codes are append-only per the evolution contract.\n// `EcsErrorCode` (declared at the foot of this file) merges all literal codes\n// into one closed union; downstream `switch (err.code)` is exhaustive.\n//\n// Every detail object is a discriminated payload — narrowed per `.code` via\n// `EcsErrorDetail` (also at foot of file).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Thrown / returned via `Result.err` when UniqueRefStore.alloc encounters a\n * slot whose refcount has already dropped to zero (sentinel for a released\n * slot reused without re-init).\n *\n * `.code = 'unique-ref-released'`\n * `.detail = { handle, target }`\n * `.hint` — recommends checking handle lifetime against owner despawn.\n */\nexport class UniqueRefReleasedError extends Error {\n override readonly name = 'UniqueRefReleasedError';\n readonly code = 'unique-ref-released' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly handle: number; readonly target: string };\n\n constructor(handle: number, target: string) {\n const hint = `Handle ${handle} (target ${target}) was released before this access. Re-acquire via the producing system or re-spawn the asset before reading.`;\n const expected = 'live (refcount >= 1) managed handle';\n super(\n `UniqueRefStore: handle is already released.\\n` +\n ` code: unique-ref-released\\n` +\n ` handle: ${handle}\\n` +\n ` target: ${target}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { handle, target };\n }\n}\n\n/**\n * Thrown / returned via `Result.err` when UniqueRefStore.release is called on\n * a handle whose refcount is already zero (double-free).\n *\n * `.code = 'unique-ref-double-release'`\n * `.detail = { handle, target }`\n * `.hint` — recommends auditing the release-loop entry points (set / removeComponent / despawn).\n */\nexport class UniqueRefDoubleReleaseError extends Error {\n override readonly name = 'UniqueRefDoubleReleaseError';\n readonly code = 'unique-ref-double-release' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly handle: number; readonly target: string };\n\n constructor(handle: number, target: string) {\n const hint = `Handle ${handle} (target ${target}) was released twice. Only one of {despawn / removeComponent / set} should release a managed handle per lifecycle.`;\n const expected = 'first release of a managed handle (refcount transition 1 -> 0)';\n super(\n `UniqueRefStore: double release on handle.\\n` +\n ` code: unique-ref-double-release\\n` +\n ` handle: ${handle}\\n` +\n ` target: ${target}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { handle, target };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260614-ecs-shared-component-and-unique-rename M3 — SharedRefStore\n// closed-union extension (+2). Mirrors the UniqueRef* pair — `'shared-ref-released'`\n// covers resolve-after-release / retain-after-release; `'shared-ref-double-release'`\n// covers release-when-rc-already-zero. Both are Result.err returns (not throws);\n// AI users branch on `.code` and read `.detail.handle` for the offending slot.\n//\n// Detail field shape mirrors UniqueRef* with one addition (`rc`) so AI users\n// debugging a double-release see the exact rc transition that surfaced the\n// failure (charter P3 progressive disclosure). Empty `target` handled the\n// same way as UniqueRef* — runtime-erased phantom, surfaced as '<unknown>'.\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Thrown / returned via `Result.err` when SharedRefStore.resolve / .retain is\n * called on a handle whose refcount has already dropped to zero (slot released).\n *\n * `.code = 'shared-ref-released'`\n * `.detail = { handle, target }`\n * `.hint` — recommends checking handle lifetime against owner / consumer release.\n */\nexport class SharedRefReleasedError extends Error {\n override readonly name = 'SharedRefReleasedError';\n readonly code = 'shared-ref-released' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly handle: number; readonly target: string };\n\n constructor(handle: number, target: string) {\n const hint = `Handle ${handle} (target ${target}) was released (refcount reached 0). Re-acquire via the producing system or re-spawn the asset before reading.`;\n const expected = 'live (refcount >= 1) shared handle';\n super(\n `SharedRefStore: handle is already released.\\n` +\n ` code: shared-ref-released\\n` +\n ` handle: ${handle}\\n` +\n ` target: ${target}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { handle, target };\n }\n}\n\n/**\n * Thrown / returned via `Result.err` when SharedRefStore.release is called on\n * a handle whose refcount is already zero (double-release). Distinct from the\n * UniqueRef family because shared release is rc--, not direct slot drop —\n * AI users debug this by reading `.detail.rc` (always 0 here) alongside the\n * payload-presence signal.\n *\n * `.code = 'shared-ref-double-release'`\n * `.detail = { handle, target, rc }`\n * `.hint` — recommends auditing the producer / consumer release pairs.\n */\nexport class SharedRefDoubleReleaseError extends Error {\n override readonly name = 'SharedRefDoubleReleaseError';\n readonly code = 'shared-ref-double-release' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly handle: number; readonly target: string; readonly rc: number };\n\n constructor(handle: number, target: string, rc: number) {\n const hint = `Handle ${handle} (target ${target}) released with rc=${rc}. Each shared handle must have a matching alloc/retain for every release; audit the producer / consumer release pairs.`;\n const expected = 'rc >= 1 before release';\n super(\n `SharedRefStore: double release on handle.\\n` +\n ` code: shared-ref-double-release\\n` +\n ` handle: ${handle}\\n` +\n ` target: ${target}\\n` +\n ` rc: ${rc}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { handle, target, rc };\n }\n}\n\n/** Raised before SharedRefStore mutates slot state for a nullish payload. */\nexport class SharedRefPayloadInvalidError extends Error {\n override readonly name = 'SharedRefPayloadInvalidError';\n readonly code = 'shared-ref-payload-invalid' as const;\n readonly expected = 'a non-null, non-undefined shared payload';\n readonly hint = 'Allocate a concrete payload and let its owning effect dispose it.';\n readonly detail: { readonly target: string; readonly actual: 'null' | 'undefined' };\n\n constructor(target: string, actual: 'null' | 'undefined') {\n super(`SharedRefStore: ${actual} payload is not a valid shared reference for ${target}.`);\n this.detail = { target, actual };\n }\n}\n\n/**\n * Returned via `Result.err` when a builtin-tier slot (`slot < BUILTIN_BASE`)\n * is passed to SharedRefStore.alloc / retain / release / resolve\n * (feat-20260614 M6 D-15). The SharedRefStore manages ONLY user-tier slots\n * (`>= BUILTIN_BASE`); builtin asset payloads are process-static and owned by\n * the package that authored their builtin handle, never reference-counted by\n * this World store.\n *\n * `.code = 'builtin-slot-not-owned'`\n * `.detail = { slot }`\n * `.hint` — points the caller back to the builtin handle owner.\n */\nexport class BuiltinSlotNotOwnedError extends Error {\n override readonly name = 'BuiltinSlotNotOwnedError';\n readonly code = 'builtin-slot-not-owned' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly slot: number };\n\n constructor(slot: number) {\n const hint = `Slot ${slot} is a builtin-tier handle (< BUILTIN_BASE). World.sharedRefs manages only user-tier handles (>= BUILTIN_BASE). Obtain the builtin payload from the package that authored the handle; builtin payloads are process-static and never reference-counted by this World.`;\n const expected = 'user-tier slot (>= BUILTIN_BASE)';\n super(\n `SharedRefStore: builtin slot is not owned by this store.\\n` +\n ` code: builtin-slot-not-owned\\n` +\n ` slot: ${slot}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { slot };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260623-asset-handle-generation M4 — stale error classes (+2).\n//\n// Two error classes covering gen-based staleness detection in SharedRefStore\n// and UniqueRefStore. Distinguish from the existing `*-ref-released` codes\n// (slot empty / never allocated) — `*-ref-stale` means the slot has been\n// released AND re-allocated, so the caller's handle generation no longer\n// matches the store's current generation. AI users pick different recovery\n// strategies: released -> re-load the asset; stale -> re-acquire the handle\n// from AssetRegistry (charter P3 explicit failure, two semantics two\n// recovery paths).\n//\n// `.detail` carries { slot, expectedGeneration, actualGeneration } aligned\n// with StaleEntityError field names (AC-11). Codes are add-only minor\n// members of EcsErrorCode (AC-10) and detail shapes extend EcsErrorDetail\n// discriminator.\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` when SharedRefStore.resolve / .retain / .release\n * is called with a handle whose generation no longer matches the store's\n * current generation for that slot — the slot was released and re-allocated\n * to a different payload. Distinct from `'shared-ref-released'` (slot empty,\n * never re-allocated): stale means the slot IS live but belongs to a newer\n * allocation.\n *\n * `.code = 'shared-ref-stale'`\n * `.detail = { slot, expectedGeneration, actualGeneration }`\n * `.hint` — recommends re-acquiring the handle from AssetRegistry.\n */\nexport class SharedRefStaleError extends Error {\n override readonly name = 'SharedRefStaleError';\n readonly code = 'shared-ref-stale' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly slot: number;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n };\n\n constructor(slot: number, expectedGeneration: number, actualGeneration: number) {\n const hint = `Handle for slot ${slot} is stale: expected generation ${expectedGeneration}, but the store has generation ${actualGeneration} (slot was released and re-allocated). Re-acquire the handle from AssetRegistry.`;\n const expected = `generation === ${actualGeneration} (current store generation)`;\n super(\n `SharedRefStore: stale handle.\\n` +\n ` code: shared-ref-stale\\n` +\n ` slot: ${slot}\\n` +\n ` expectedGeneration: ${expectedGeneration}\\n` +\n ` actualGeneration: ${actualGeneration}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { slot, expectedGeneration, actualGeneration };\n }\n}\n\n/**\n * Returned via `Result.err` when UniqueRefStore.resolve / .release is\n * called with a handle whose generation no longer matches the store's\n * current generation for that slot — the slot was released and re-allocated.\n * Distinct from `'unique-ref-released'` (slot empty). UniqueRefStore has\n * no retain method; the stale surface is resolve + release only.\n *\n * `.code = 'unique-ref-stale'`\n * `.detail = { slot, expectedGeneration, actualGeneration }`\n * `.hint` — recommends re-acquiring the handle from the producing system.\n */\nexport class UniqueRefStaleError extends Error {\n override readonly name = 'UniqueRefStaleError';\n readonly code = 'unique-ref-stale' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly slot: number;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n };\n\n constructor(slot: number, expectedGeneration: number, actualGeneration: number) {\n const hint = `Handle for slot ${slot} is stale: expected generation ${expectedGeneration}, but the store has generation ${actualGeneration} (slot was released and re-allocated). Re-acquire the handle via the producing system or re-spawn the asset.`;\n const expected = `generation === ${actualGeneration} (current store generation)`;\n super(\n `UniqueRefStore: stale handle.\\n` +\n ` code: unique-ref-stale\\n` +\n ` slot: ${slot}\\n` +\n ` expectedGeneration: ${expectedGeneration}\\n` +\n ` actualGeneration: ${actualGeneration}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { slot, expectedGeneration, actualGeneration };\n }\n}\n\n/**\n * Thrown / returned via `Result.err` when BufferPool indexing reads or writes\n * an offset outside the slot's `[0, size)` byte range. Triggers are limited\n * to the `buffer:<N>` and managed-array-element-buffer paths; the\n * `'string'` schema vocab no longer routes through this code (collapsed onto\n * the managed-ref dispatch by feat-20260515-string-managed-collapse — JS\n * string capacity is bounded by the host runtime, not by BufferPool buckets).\n *\n * `.code = 'managed-buffer-out-of-bounds'`\n * `.detail = { index, size }`\n * `.hint` — points at the field's `'buffer'` / `buffer<N>` schema declaration.\n */\nexport class ManagedBufferOutOfBoundsError extends RangeError {\n override readonly name = 'ManagedBufferOutOfBoundsError';\n readonly code = 'managed-buffer-out-of-bounds' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly index: number; readonly size: number };\n\n constructor(index: number, size: number) {\n const hint = `Index ${index} is outside [0, ${size}). Check the field's 'buffer' / 'buffer<N>' declaration matches the access pattern.`;\n const expected = `index in [0, ${size})`;\n super(\n `BufferPool: index out of bounds.\\n` +\n ` code: managed-buffer-out-of-bounds\\n` +\n ` index: ${index}\\n` +\n ` size: ${size}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { index, size };\n }\n}\n\n/**\n * Thrown / returned via `Result.err` when BufferPool resize is asked to shrink\n * a slot below its current allocated size — the pool only grows.\n *\n * `.code = 'managed-buffer-shrink-not-supported'`\n * `.detail = { requested, current }`\n * `.hint` — directs callers to allocate a fresh slot if a smaller buffer is needed.\n */\nexport class ManagedBufferShrinkNotSupportedError extends Error {\n override readonly name = 'ManagedBufferShrinkNotSupportedError';\n readonly code = 'managed-buffer-shrink-not-supported' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly requested: number; readonly current: number };\n\n constructor(requested: number, current: number) {\n const hint = `BufferPool only grows. Requested ${requested} bytes < current ${current}; allocate a fresh slot if a smaller buffer is required.`;\n const expected = `requested >= ${current}`;\n super(\n `BufferPool: shrink not supported.\\n` +\n ` code: managed-buffer-shrink-not-supported\\n` +\n ` requested: ${requested}\\n` +\n ` current: ${current}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { requested, current };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260515-buffer-array-vocab-collapse w11 — closed-union evolution.\n//\n// 4 managed-array-* error classes deleted (replaced by the 4 collapsed-vocab\n// codes below); ManagedArrayElementTypeNotAllowedError preserved (still\n// surfaced from defineComponent's schema parser).\n//\n// 2 surviving error classes:\n// - FixedSizeMismatchError ('fixed-size-mismatch')\n// - InstanceTransformsStrideMismatchError ('instance-transforms-stride-mismatch')\n//\n// Naming-prefix orthogonality (plan-strategy §2.5):\n// fixed- element-type or capacity contract violations on fixed shape\n// array- operation failures (pop on empty) on the array vocab keyword\n// instance- GPU-render component-specific stride contract (Instances.transforms)\n//\n// The array element-wise facade was removed; only write-shape and render\n// stride errors remain on this boundary. The\n// `instance-transforms-stride-mismatch` member is the plan-strategy §2.4\n// evolution surfaced from `packages/runtime/src/render-system-extract.ts`\n// defensive entry (consumed by w15 in M3, but the error class lives here so\n// the EcsErrorCode union closure is owned by ECS — RhiError is not extended\n// per plan-strategy §2.4 decision).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` from `world.set` when a `buffer<N>` field is\n * written with a `Uint8Array` whose `byteLength` does not equal the\n * schema-declared fixed size `N`. AI users resize their payload to exactly\n * `N` bytes (zero-pad or truncate at the producer) before calling `world.set`.\n *\n * `.code = 'fixed-size-mismatch'`\n * `.detail = { expected, actual }`\n * `.hint` — points at the producer's payload sizing.\n */\nexport class FixedSizeMismatchError extends Error {\n override readonly name = 'FixedSizeMismatchError';\n readonly code = 'fixed-size-mismatch' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly expected: number; readonly actual: number };\n\n constructor(fieldName: string, expected: number, actual: number) {\n const hint = `buffer<${expected}> set with byteLength ${actual} (expected ${expected}); resize your Uint8Array to exactly ${expected} bytes before world.set`;\n const expectedStr = `byteLength === ${expected}`;\n super(\n `buffer<N>: fixed-size mismatch.\\n` +\n ` code: fixed-size-mismatch\\n` +\n ` field: ${fieldName}\\n` +\n ` expected: ${expected}\\n` +\n ` actual: ${actual}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expectedStr;\n this.detail = { expected, actual };\n }\n}\n\n/**\n * Surfaced via the Layer-3 ErrorHandler from\n * `packages/runtime/src/render-system-extract.ts` defensive entry when an\n * `Instances.transforms` array<f32> length violates the column-major mat4\n * stride contract (`length % 16 === 0`). Locates the failure adjacent to the\n * extract pipeline rather than at GPU upload (plan-strategy §2.4 D-P2 +\n * §8.3 hint SSOT).\n *\n * `.code = 'instance-transforms-stride-mismatch'`\n * `.detail = { actualLength, expectedStride: 16 }`\n * `.hint` — names the stride invariant + the call sites to audit.\n */\nexport class InstanceTransformsStrideMismatchError extends Error {\n override readonly name = 'InstanceTransformsStrideMismatchError';\n readonly code = 'instance-transforms-stride-mismatch' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly actualLength: number; readonly expectedStride: 16 };\n\n constructor(actualLength: number) {\n const hint = `Instances.transforms length ${actualLength} violates stride 16 (mat4); ensure transforms.length % 16 === 0 before render frame; verify world.set / world.push call sites`;\n const expectedStr = 'actualLength % 16 === 0';\n super(\n `Instances.transforms: stride mismatch.\\n` +\n ` code: instance-transforms-stride-mismatch\\n` +\n ` actualLength: ${actualLength}\\n` +\n ` expectedStride: 16\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expectedStr;\n this.detail = { actualLength, expectedStride: 16 };\n }\n}\n\n/**\n * Thrown by `defineComponent` when an `array<T>` / `array<T,N>` schema field\n * carries an element type outside the legal whitelist (scalars + entity).\n * Forms like `array<ref<X>>` / `array<handle<X>>` / `array<buffer:N>` /\n * `array<array<...>>` are rejected (AC-03 runtime fail-safe). The TS layer\n * blocks these forms at compile time; this error is the runtime backstop for\n * `as unknown as SchemaFieldType` casts.\n *\n * `.code = 'managed-array-element-type-not-allowed'`\n * `.detail = { fieldName, elementType, hint }`\n * `.hint` — lists the whitelist of legal element types.\n */\nexport class ManagedArrayElementTypeNotAllowedError extends Error {\n override readonly name = 'ManagedArrayElementTypeNotAllowedError';\n readonly code = 'managed-array-element-type-not-allowed' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly fieldName: string;\n readonly elementType: string;\n readonly hint: string;\n };\n\n constructor(fieldName: string, elementType: string) {\n const hint = `array<T> element type must be a scalar (f32/f64/i32/u32/i16/u16/i8/u8/bool/enum/ref) or entity. ref<X> / handle<X> / buffer:N / nested array<...> are forbidden on field \"${fieldName}\".`;\n const expected = 'element type in {scalar | entity}';\n super(\n `managed-array: element type not allowed.\\n` +\n ` code: managed-array-element-type-not-allowed\\n` +\n ` field: ${fieldName}\\n` +\n ` elementType: ${elementType}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { fieldName, elementType, hint };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// EcsErrorCode closed union (w5)\n//\n// Merges every `.code` literal across the EcsError family. Downstream\n// `switch (err.code)` blocks become exhaustive; `assertNever(code)` catches\n// any future code addition without a matching case at compile time.\n//\n// Order is grouped (legacy SCREAMING_SNAKE first, then closed-set kebab) but\n// not load-bearing — TS unions are unordered.\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Closed union of every `.code` literal carried by EcsError instances. */\nexport type EcsErrorCode =\n // Legacy SCREAMING_SNAKE codes (7, carried unchanged; the two\n // registration codes COMPONENT_ALREADY_REGISTERED / COMPONENT_NOT_REGISTERED\n // were dropped by feat-20260602 along with the per-World register concept).\n | 'stale-entity'\n | 'component-already-present'\n | 'component-not-present'\n | 'cyclic-dependency'\n | 'resource-not-found'\n // ECS time and schedule-scope errors (M2 w16, approved 43 -> 46 baseline; verify hotfix +1 → 47).\n | 'time-delta-invalid'\n | 'time-config-invalid'\n | 'schedule-scope-mismatch'\n // ScheduleMutationError closed-set kebab code.\n | ScheduleMutationErrorCode\n // w5 managed-* kebab codes (4).\n | 'unique-ref-released'\n | 'unique-ref-double-release'\n // feat-20260614-ecs-shared-component-and-unique-rename M3 — SharedRefStore\n // closed-union extension (+2). `'shared-ref-released'` covers resolve / retain\n // on rc=0; `'shared-ref-double-release'` covers release on rc=0.\n | 'shared-ref-released'\n | 'shared-ref-double-release'\n | 'shared-ref-payload-invalid'\n // feat-20260614-ecs-shared-component-and-unique-rename M6 D-15 (+1).\n // SharedRefStore manages ONLY user-tier slots (>= BUILTIN_BASE); a builtin\n // slot (< BUILTIN_BASE) passed to alloc/retain/release/resolve is a caller\n // error -> `'builtin-slot-not-owned'` (the authoring package owns the payload).\n | 'builtin-slot-not-owned'\n // feat-20260623-asset-handle-generation M4 — stale error codes (+2).\n // `'shared-ref-stale'` / `'unique-ref-stale'` cover gen mismatch on resolve /\n // retain / release after slot re-allocation. Add-only minor per AGENTS.md\n // Error model evolution contract; distinct from the existing `*-ref-released`\n // codes (slot empty vs slot re-allocated).\n | 'shared-ref-stale'\n | 'unique-ref-stale'\n | 'managed-buffer-out-of-bounds'\n | 'managed-buffer-shrink-not-supported'\n // managed-array-* kebab codes — surviving member from feat-20260514;\n // the other 4 (`managed-array-{index-out-of-bounds, pop-empty,\n // shrink-not-supported, stride-mismatch}`) were dropped by\n // feat-20260515-buffer-array-vocab-collapse w11 in favour of the 4 new\n // collapsed-vocab codes below. Kept here because `defineComponent`'s schema\n // parser still surfaces it for illegal `array<...>` element types.\n // feat-20260515-buffer-array-vocab-collapse w11 collapsed-vocab codes (4,\n // plan-strategy §2.4 + §2.5 four-prefix taxonomy).\n | 'fixed-size-mismatch'\n // feat-20260519-light-casters-point-spot-pbr w2 — PointLight / SpotLight\n // spawn-time payload bound violation (plan-strategy D-S3 a). 23 -> 24\n // minor evolution per AGENTS.md Error model evolution contract.\n // feat-20260520-2d-sprite-layer-mvp M-2 w13 — resource-setter bound\n // validation (plan-strategy D-4). 25 -> 26 minor evolution; first\n // consumer is `setTransparentSortConfig` (mode ∈ {0, 1, 2}).\n // feat-20260521-sprite-atlas-animation M1 T-05 — spriteAnimationTickSystem\n // runtime invariant violation (plan-strategy D-1). 26 -> 27 minor evolution\n // per AGENTS.md §Error model evolution contract; same-shape mirror of\n // 'spawn-light-invalid-bounds' (feat-20260519 w2) and 'resource-invalid-\n // value' (feat-20260520 w13) — the `<noun>-invalid-...` kebab series keeps\n // switch (err.code) narrows visually consistent for AI users (charter P4).\n // feat-20260531-ecs-relationship-abstraction-bidirectional-sync M2 —\n // relationship bidirectional sync + defineComponent relationship validation +\n // addChild/reparent cycle detection + removeChild detach guard\n // (plan-strategy D-5). 27 -> 31 minor evolution per AGENTS.md Error model\n // evolution contract. `relationship-exclusive-violation` is NOT a member:\n // exclusive re-add is an automatic reparent (success path), not an error.\n | 'relationship-self-cycle'\n | 'relationship-detach-mismatch'\n // feat-20260602-drop-component-registration w16-a — scene instantiate\n // fail-fast when a SceneAsset entity names a component that was never defined\n // via defineComponent (the per-World register concept was dropped; a\n // component becomes globally usable the moment defineComponent runs). 30 ->\n // 31 minor evolution per AGENTS.md Error model evolution contract. Replaces\n // the deleted COMPONENT_NOT_REGISTERED code at the scene-instance producer\n // sites (research Finding 5 missed these 3 producers; human escalation-\n // response authorized this scope-amendment).\n // feat-20260602-archetype-stores-full-packed-entity M1 / w3 — removeComponent\n // rejection when the target is an essential (undeletable) component. The only\n // essential component is the id=0 `Entity` (plan-strategy D-3). Net +1 minor\n // evolution per AGENTS.md §Error model evolution contract.\n | 'remove-essential-component'\n // feat-20260608-scene-nesting-ecs-fication M1 / w9 — setSceneOverride\n // type-mismatch fail-fast (plan-strategy D-9). 30 -> 31 minor evolution per\n // AGENTS.md §Error model evolution contract. Surfaced from\n // `world.setSceneOverride(root, member, comp, field, value)` when `value`'s\n // runtime type does not match the per-component schema field type (the\n // override apply path never silently coerces — value writes are typed at the\n // ECS layer; requirements §Edge cases table last row, reviewer Issue 1).\n // bug-20260615-spawn-data-unknown-field-fail-fast — spawn / addComponent /\n // SceneAsset.instantiate / Commands.spawn fail-fast when the caller-supplied\n // payload carries a key that is not declared in the component schema. Pre-\n // fix the unknown key was silently dropped by `fillComponentDefaults`\n // (which iterated only over schema keys), routing typos like\n // `MeshRenderer { material }` (singular legacy name) into the empty-default\n // path and producing invisible / mid-grey entities downstream. AI users\n // narrow on `.code` then read `.detail.field` for the offending key and\n // `.detail.knownFields` for the valid field whitelist.\n | 'spawn-data-unknown-field'\n // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —\n // SpriteInstances primitive + tilemap terrain static-batch path. Three\n // codes declared together; fire path lands in M3 w13 at the\n // render-system-extract QueryRow loop. Minor evolution +3 per\n // AGENTS.md §Error model evolution contract; plan-strategy D-6 keeps the\n // detection in the render domain (not the ECS spawn path) to avoid an\n // ECS -> AssetRegistry reverse dep for the shader-id lookup.\n // feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —\n // P3 shared-field value gate. A `shared<T>` scalar or `array<shared<T>>`\n // element must be a resolved numeric Handle; a raw GUID string / `{ guid }` /\n // `{ kind }` object (the pre-resolution shape an AI user gets from a sidecar)\n // was silently coerced to the all-zero sentinel by the column packer\n // (`typed[i] = typeof val === 'number' ? val : 0`) / scalar write, so a\n // mis-bound reference read back as `0` / `[0,0,0,0]` and rendered blank with\n // no error. `validateComponentDataKeys` only checks key names, not value\n // types — this code closes the value-type gap at all three write entries\n // (spawn / addComponent / set). AI users resolve a GUID via\n // `AssetRegistry.load(guid, kind) + allocSharedRef` first; passing the raw GUID now fails fast.\n // Minor evolution +1 per AGENTS.md §Error model evolution contract.\n | 'shared-field-invalid-value'\n // feat-20260714-bevy-style-system-sets M1 / w3 — sole invalid-SystemSet\n // error code. Surfaced from world.addSystems when a\n // token fails identity validation (brand bypass + registry identity check).\n // Minor evolution +1 per AGENTS.md §Error model evolution contract.\n | 'system-set-not-registered'\n // Closed enum field writes fail before archetype or column mutation.\n | 'component-field-invalid-value'\n | 'component-numeric-value-invalid'\n | 'managed-array-invalid-value'\n | 'shared-kernel-ineligible'\n | 'shared-kernel-failed'\n | 'world-poisoned'\n // World.update terminal failures. These remain in the same closed union as\n // structural errors so consumers never need a second error discriminator.\n | 'command-failed'\n | 'system-failed';\n\n/**\n * Discriminated `.detail` payload per `.code`.\n *\n * Narrowed via `switch (err.code)` against `EcsErrorCode`. Empty-detail entries\n * (legacy errors without `.detail`) are intentionally omitted from this map —\n * only the w5 family + `cyclic-injection` carry structured payloads today.\n */\nexport type EcsErrorDetail =\n | {\n readonly code: 'shared-kernel-ineligible';\n readonly kernelName: string;\n readonly reason: string;\n }\n | {\n readonly code: 'shared-kernel-failed';\n readonly kernelName: string;\n readonly worldIdentity: string;\n readonly cause: unknown;\n readonly partialWrite: boolean;\n readonly retryable: false;\n }\n | { readonly code: 'world-poisoned'; readonly worldIdentity: string; readonly fault: unknown }\n | { readonly code: 'sparse-storage-requires-tag'; readonly componentName: string }\n | {\n readonly code: 'query-descriptor-conflict';\n readonly componentName: string;\n readonly roles: readonly string[];\n }\n | { readonly code: 'query-data-requires-fields'; readonly componentName: string }\n | { readonly code: 'query-span-unavailable'; readonly reason: QuerySpanUnavailableReason }\n | {\n readonly code: 'query-iteration-invalidated';\n readonly expectedStructureEpoch: number;\n readonly actualStructureEpoch: number;\n }\n | { readonly code: 'query-iteration-active' }\n | { readonly code: 'change-epoch-exhausted'; readonly epoch: number }\n | { readonly code: 'unique-ref-released'; readonly handle: number; readonly target: string }\n | {\n readonly code: 'unique-ref-double-release';\n readonly handle: number;\n readonly target: string;\n }\n // feat-20260614 M3 — SharedRefStore detail variants (+2).\n | { readonly code: 'shared-ref-released'; readonly handle: number; readonly target: string }\n | {\n readonly code: 'shared-ref-double-release';\n readonly handle: number;\n readonly target: string;\n readonly rc: number;\n }\n | {\n readonly code: 'shared-ref-payload-invalid';\n readonly target: string;\n readonly actual: 'null' | 'undefined';\n }\n // feat-20260614 M6 D-15 — builtin-slot fail-fast detail variant (+1).\n | { readonly code: 'builtin-slot-not-owned'; readonly slot: number }\n // feat-20260623-asset-handle-generation M4 — stale error detail variants (+2).\n | {\n readonly code: 'shared-ref-stale';\n readonly slot: number;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n }\n | {\n readonly code: 'unique-ref-stale';\n readonly slot: number;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n }\n | { readonly code: 'managed-buffer-out-of-bounds'; readonly index: number; readonly size: number }\n | {\n readonly code: 'managed-buffer-shrink-not-supported';\n readonly requested: number;\n readonly current: number;\n }\n // feat-20260514 surviving managed-array-* discriminated detail variant (1).\n | {\n readonly code: 'managed-array-element-type-not-allowed';\n readonly fieldName: string;\n readonly elementType: string;\n readonly hint: string;\n }\n // feat-20260515-buffer-array-vocab-collapse w11 collapsed-vocab detail\n // variants (4). Per-code field names are SSOT-anchored at AC-07 + plan-\n // strategy §2.4 §detail-list (NOT renamed for \"consistency\" — name follows\n // semantics).\n | {\n readonly code: 'fixed-size-mismatch';\n readonly expected: number;\n readonly actual: number;\n }\n | {\n readonly code: 'instance-transforms-stride-mismatch';\n readonly actualLength: number;\n readonly expectedStride: 16;\n }\n // feat-20260519-light-casters-point-spot-pbr w2 — light and local probe\n // spawn-time payload bound violation (plan-strategy D-S3 a). detail.field\n // names the validated scalar or RGB payload while one code keeps the\n // recovery surface closed; AI users narrow on `.detail.field` after the\n // outer `switch (err.code)` to pick the specific recovery hint.\n | {\n readonly code: 'spawn-light-invalid-bounds';\n readonly field:\n | 'direction'\n | 'intensity'\n | 'color'\n | 'width'\n | 'height'\n | 'irradiance'\n | 'radius'\n | 'range'\n | 'innerOuter'\n | 'outerNinety';\n readonly got: number | readonly number[];\n }\n // feat-20260520-2d-sprite-layer-mvp M-2 w13 — resource-setter bound\n // violation (plan-strategy D-4). receivedMode carries the rejected\n // payload number; receivedKey is optional so future resource\n // validators can share the same code while disambiguating which\n // resource produced the failure.\n | {\n readonly code: 'resource-invalid-value';\n readonly receivedMode: number;\n readonly receivedKey?: string;\n }\n // feat-20260521-sprite-atlas-animation M1 T-05 — sprite-animation tick\n // runtime invariant violation (plan-strategy D-1 + section 5 AC-09).\n // detail.field two-branch keeps the regions-length / frame-duration\n // invariants under one code; AI users narrow on `.detail.field` after\n // the outer `switch (err.code)` to pick the specific recovery hint\n // (charter P3 + P4). Two top-level variants give each `.field` branch\n // its own required sub-field shape so AI users get strong narrowing\n // inside `switch (err.detail.field)` without optional sub-fields\n // bleeding across branches.\n | {\n readonly code: 'sprite-animation-invalid';\n readonly field: 'regions-length';\n readonly regionsLength: number;\n readonly frameCount: number;\n }\n | {\n readonly code: 'sprite-animation-invalid';\n readonly field: 'frame-duration';\n readonly frameDuration: number;\n }\n // feat-20260531-ecs-relationship-abstraction-bidirectional-sync M2 — the 4\n // relationship-* discriminated detail variants (plan-strategy D-5). Each\n // carries the component name + the entities involved so AI users narrow on\n // `.code` then read `.detail` to locate the offending relationship surface.\n | {\n readonly code: 'relationship-self-cycle';\n readonly component: string;\n readonly entity: number;\n readonly ancestor: number;\n }\n | {\n readonly code: 'relationship-mirror-component-not-registered';\n readonly component: string;\n readonly mirror: string;\n }\n | {\n readonly code: 'relationship-mirror-field-type-mismatch';\n readonly component: string;\n readonly mirror: string;\n readonly field: string;\n readonly actualType: string;\n }\n | {\n readonly code: 'relationship-detach-mismatch';\n readonly component: string;\n readonly child: number;\n readonly expectedParent: number;\n readonly actualParent: number;\n }\n // feat-20260602-drop-component-registration w16-a — scene instantiate\n // unknown-component fail-fast (30 -> 31). `.detail.name` carries the\n // component name that was never defined via defineComponent.\n | {\n readonly code: 'component-not-defined';\n readonly name: string;\n }\n // feat-20260602-archetype-stores-full-packed-entity M1 / w3 — removeComponent\n // essential-component rejection. `.detail.componentName` carries the essential\n // component name (the id=0 `Entity`).\n | {\n readonly code: 'remove-essential-component';\n readonly componentName: string;\n }\n // feat-20260608-scene-nesting-ecs-fication M1 / w9 — setSceneOverride\n // value-type rejection (plan-strategy D-9; requirements §Edge cases last\n // row + reviewer Issue 1). `.detail.comp` / `.detail.field` locate the\n // override target; `.detail.expectedType` carries the schema-declared\n // type literal (e.g. 'f32', 'bool', 'string'); `.detail.actualType`\n // carries the runtime `typeof value` (typed `unknown` because the\n // override write is not coerced — fail-fast surfaces the mismatch).\n | {\n readonly code: 'scene-override-type-mismatch';\n readonly comp: string;\n readonly field: string;\n readonly expectedType: string;\n readonly actualType: unknown;\n }\n // bug-20260615-spawn-data-unknown-field-fail-fast — spawn-data unknown-key\n // fail-fast. `.detail.component` names the schema's component, `.detail.field`\n // is the offending raw key, `.detail.knownFields` is the schema's full field\n // whitelist (sorted, used by AI users / hint formatters to surface \"did you\n // mean\" suggestions without round-tripping to the schema).\n | {\n readonly code: 'spawn-data-unknown-field';\n readonly component: string;\n readonly field: string;\n readonly knownFields: readonly string[];\n }\n // feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —\n // 3 discriminated detail variants for the SpriteInstances primitive\n // (declared in M1, fired at render-system-extract entry in M3).\n | {\n readonly code: 'sprite-instances-count-mismatch';\n readonly transformsLength: number;\n readonly regionsLength: number;\n readonly expectedStride: { readonly transforms: 16; readonly regions: 4 };\n }\n | {\n readonly code: 'sprite-instances-requires-sprite-shader';\n readonly entityId: number;\n readonly observedMaterialShaderId: string;\n }\n | {\n readonly code: 'sprite-instances-mutually-exclusive-with-instances';\n readonly entityId: number;\n }\n // feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —\n // shared-field value gate. `.detail.component` / `.detail.field` locate the\n // shared reference field; `.detail.fieldType` is the schema-declared type\n // literal (`shared<T>` scalar or `array<shared<T>>`); `.detail.actualValue`\n // is the offending non-handle value (typed `unknown` — a raw GUID string /\n // `{ guid }` / `{ kind }` object is not coerced, the fail-fast surfaces it);\n // `.detail.index` is the array element index for the array form (undefined for\n // the scalar form). AI users read `.detail.field` + `.detail.fieldType` to see\n // which reference needs `AssetRegistry.load(guid, kind) + allocSharedRef` before binding.\n | {\n readonly code: 'shared-field-invalid-value';\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n readonly index?: number;\n }\n // feat-20260714-bevy-style-system-sets M1 / w3 — invalid-SystemSet detail.\n // `.detail.name` is the rejected token name; `.detail.registered` is a\n // deterministic snapshot of the current registry keys.\n | {\n readonly code: 'system-set-not-registered';\n readonly name: string;\n readonly registered: readonly string[];\n }\n | {\n readonly code: 'component-field-invalid-value';\n readonly entity: number | undefined;\n readonly component: string;\n readonly field: string;\n readonly received: unknown;\n readonly allowedValues: Readonly<Record<string, number>>;\n }\n | {\n readonly code: 'component-numeric-value-invalid';\n readonly entity: number | undefined;\n readonly component: string;\n readonly field: string;\n readonly received: number;\n readonly index?: number;\n }\n | {\n readonly code: 'managed-array-invalid-value';\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n }\n // feat-20260714-bevy-style-system-sets M2 / w12 — structured cyclic-dependency\n // detail. `.detail.cycle` is the ordered cycle path array; consumers read\n // this instead of parsing the message string.\n | {\n readonly code: 'cyclic-dependency';\n readonly cycle: readonly string[];\n }\n | {\n readonly code: 'command-failed';\n readonly systemName: string;\n readonly schedule: string;\n readonly commandIndex: number;\n readonly commandKind: CommandKind;\n readonly cause: unknown;\n }\n | {\n readonly code: 'system-failed';\n readonly systemName: string;\n readonly schedule: string;\n readonly cause: unknown;\n readonly lastCommittedCommand: CommandCommitEvidence | null;\n };\n\n/**\n * Layer-3 error envelope routed through managed-storage callbacks. Callers\n * narrow `detail` through the source-owned `EcsErrorDetail` union.\n */\nexport interface ManagedArrayErrorEnvelope {\n readonly code: EcsErrorCode;\n readonly hint: string;\n readonly expected: string;\n readonly detail: unknown;\n}\n\nexport type CommandKind = 'spawn' | 'despawn' | 'addComponent' | 'removeComponent';\n\nexport interface CommandCommitEvidence {\n readonly index: number;\n readonly kind: CommandKind;\n}\n\n/** Expected command preflight failure, with the exact batch location. */\nexport class CommandFailedError extends Error {\n override readonly name = 'CommandFailedError';\n readonly code = 'command-failed' as const;\n readonly expected = 'all deferred commands pass preflight before commit';\n readonly hint =\n 'Inspect detail.cause, repair the command at detail.commandIndex, and run the World again.';\n override readonly cause: unknown;\n readonly detail: {\n readonly systemName: string;\n readonly schedule: string;\n readonly commandIndex: number;\n readonly commandKind: CommandKind;\n readonly cause: unknown;\n };\n\n constructor(\n systemName: string,\n schedule: string,\n commandIndex: number,\n commandKind: CommandKind,\n cause: unknown,\n ) {\n super(\n `Deferred command failed before commit in ${schedule}/${systemName} ` +\n `at command ${commandIndex} (${commandKind}).`,\n );\n this.cause = cause;\n this.detail = { systemName, schedule, commandIndex, commandKind, cause };\n }\n}\n\n/** Unknown system failure. The World is poisoned because row writes may exist. */\nexport class SystemFailedError extends Error {\n override readonly name = 'SystemFailedError';\n readonly code = 'system-failed' as const;\n readonly expected = 'a system completes without throwing or returning a failed Result';\n readonly hint =\n 'Inspect detail.cause, stop using this poisoned World, and rebuild it from the owning App.';\n override readonly cause: unknown;\n readonly detail: {\n readonly systemName: string;\n readonly schedule: string;\n readonly cause: unknown;\n readonly lastCommittedCommand: CommandCommitEvidence | null;\n };\n\n constructor(\n systemName: string,\n schedule: string,\n cause: unknown,\n lastCommittedCommand: CommandCommitEvidence | null = null,\n ) {\n super(`System ${schedule}/${systemName} failed; World is poisoned.`);\n this.cause = cause;\n this.detail = { systemName, schedule, cause, lastCommittedCommand };\n }\n}\n","// @forgeax/engine-ecs — Component schema + opaque token.\n//\n// `defineComponent(name, fields, options?)` returns a frozen token carrying\n// only the three runtime facts needed by callers:\n// - `.name`: component name string\n// - `.fields`: frozen field descriptors (the schema SSOT)\n// - `.storage`: table or sparse placement\n// Numeric identity, flat schema projections, and default maps live in the ECS\n// owner tables below rather than on the public token.\n//\n// ComponentId is used by archetype storage, bitmask matching, and edges cache.\n\nimport { err, type Handle, ok, type Result } from '@forgeax/engine-types';\nimport {\n assertComponentStorage,\n deepFreeze,\n registerComponentDefinition,\n} from './component-schema';\nimport type { EntityHandle } from './entity-handle';\nimport {\n ManagedArrayElementTypeNotAllowedError,\n SchemaUnsupportedFieldError,\n SparseStorageRequiresTagError,\n} from './errors';\nimport type { ManagedColumnReader } from './storage/column';\n\n// The internal package subpath reuses this owner module so the source budget\n// does not grow a second forwarding module. Root exports remain curated in\n// index.ts; this re-export is only reached through `@forgeax/engine-ecs/internal`.\nexport { componentDefinition } from './component-schema';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Field types — schema vocab keywords (AC-01).\n//\n// Two-tier vocabulary:\n//\n// 1. Legacy scalar set: 11 keywords backed by TypedArray storage. Concrete\n// byte-sizes + TypedArray constructors are internal constants consumed by\n// `scalarRow()` to build TYPE_METADATA rows (see M4 §FIELD_SIZE_BYTES / VIEW_CTORS).\n//\n// 2. Schema-vocab keywords: 7 template-literal patterns expressing\n// ECS-managed types whose storage is owned by separate subsystems:\n// * `buffer:<bytes>` — fixed-byte managed Uint8Array, stored by BufferPool\n// * `ref<T>` — managed Handle<T,'unique'>, released by UniqueRefStore\n// * `shared<T>` — rc-tracked Handle<T,'shared'>, lifecycle owned by SharedRefStore\n// * `entity` — Entity reference (Entity | null)\n// * `string` — utf-8 string payload, allocated as a managed handle via UniqueRefStore\n// * `array<T,N>` — fixed-capacity typed view; elements inline in stride-N column (feat-20260602)\n// * `array<T>` — variable-capacity typed view over BufferPool slot bytes\n//\n// The retired `array<entity>` predecessor (closed out by this feat) is no\n// longer a valid schema field type — the union has narrowed it out.\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Bytes per element for each scalar field type. */\nconst FIELD_SIZE_BYTES = {\n f32: 4,\n f64: 8,\n i32: 4,\n u32: 4,\n i16: 2,\n u16: 2,\n i8: 1,\n u8: 1,\n bool: 1,\n enum: 4,\n ref: 4,\n} as const;\n\n/** Numeric scalar field types backed by TypedArray storage (legacy tier). */\nexport type ScalarFieldType = keyof typeof FIELD_SIZE_BYTES;\n\n/**\n * Legal element-type whitelist for the `array<T,N>` / `array<T>` vocab\n * keywords (AC-03). T must be a scalar field type, `entity`, or a\n * `shared\\<X\\>` template with a non-empty tag; reference / buffer / nested\n * array element types are forbidden (OOS-08 / OOS-03).\n *\n * feat-20260614 M5 / w23: the historical `handle\\<X\\>` element family was\n * deleted in favor of `shared\\<X\\>` (rc-tracked, lifecycle owned by\n * SharedRefStore). The `MANAGED_ARRAY_ELEMENT_TYPES` Set remains\n * static-scalar + entity only (D-8); dynamic `shared\\<X\\>` templates are\n * validated by `isValidArrayElementType` at parse time.\n *\n * Legal: every member of `ScalarFieldType` plus `entity` plus\n * `shared\\<X\\>` (non-empty tag). The `ref` legacy scalar keyword (a u32\n * column placeholder) is in the whitelist; the parametric `unique<T>` /\n * `shared<T>` scalars are rejected as array element types by AC-03.\n */\nexport type ManagedArrayElementType = ScalarFieldType | 'entity' | `shared<${string}>`;\n\n/**\n * Schema-vocab keywords beyond the legacy scalar tier (AC-01).\n *\n * Each pattern is a template-literal type so a literal schema like\n * `{ mat: 'unique<MaterialAsset>' }` types the value as\n * `Handle<'MaterialAsset','unique'>` end-to-end. Runtime acceptance is\n * gated by the internal `isSchemaVocabKeyword` check — the SSOT for parser fail-fast.\n *\n * The legacy `'buffer:<N>'` literal is retired one-cut by\n * feat-20260515-buffer-array-vocab-collapse w4: replaced by the\n * angle-bracket generic shapes `'buffer'` (variable byte slot) and\n * `'buffer<N>'` (fixed byte slot). With `'array<T>'` / `'array<T, N>'` they\n * form a 4-keyword closed surface across two orthogonal axes (element-type\n * x capacity contract).\n */\nexport type SchemaVocabKeyword =\n | 'string'\n | 'buffer'\n | `buffer<${number}>`\n | `unique<${string}>`\n | `shared<${string}>`\n | 'entity'\n | `array<${ManagedArrayElementType}, ${number}>`\n | `array<${ManagedArrayElementType}>`;\n\n/**\n * Closed union of every keyword `defineComponent` accepts for a schema field.\n * Combines the legacy scalar tier with the schema-vocab tier.\n *\n * `ComponentSchema` is keyed against this union; `defineComponent` rejects\n * any field value not satisfying it (compile-time) or matching it\n * (runtime).\n */\nexport type SchemaFieldType = ScalarFieldType | SchemaVocabKeyword;\n\n/**\n * Producer-owned semantic shape tags for authoring/schema consumers.\n *\n * The ECS storage vocabulary remains the source of truth for bytes and\n * runtime values. These tags capture the semantic shape that storage alone\n * cannot express (for example an optional entity reference or a nested\n * unique payload). The tag is deliberately closed so downstream consumers\n * can exhaustively handle the representative field-shape vocabulary without\n * creating a second component registry.\n */\nexport type FieldShapeKind =\n | 'scalar'\n | 'boolean'\n | 'enum'\n | 'vector'\n | 'quaternion'\n | 'optional'\n | 'nested'\n | 'array'\n | 'asset-ref';\n\n/**\n * Normalize any field-type keyword to its TYPE_METADATA key.\n *\n * The 11 legacy scalars round-trip their own key. The 6 vocab families normalize\n * their parametric shapes to the family key:\n * - `unique<T>` / `shared<T>` — strip `<T>` → `'ref'` / `'shared'`\n * - `buffer<N>` — strip `<N>` → `'buffer'`\n * - `array<T>` / `array<T,N>` — strip `<...>` → `'array'`\n * - `entity` / `string` / `buffer` are identity.\n *\n * Returns `null` for an unrecognised keyword so callers can skip column\n * allocation (same semantics as the retired `storageFieldType`).\n */\nexport function fieldTypeToMetaKey(fieldType: string): string | null {\n if (fieldType === 'entity' || fieldType === 'string' || fieldType === 'buffer') {\n return fieldType;\n }\n if (fieldType.startsWith('unique<') && fieldType.endsWith('>')) return 'ref';\n if (fieldType.startsWith('shared<') && fieldType.endsWith('>')) return 'shared';\n if (fieldType.startsWith('buffer<') && fieldType.endsWith('>')) return 'buffer';\n if (fieldType.startsWith('array<') && fieldType.endsWith('>')) return 'array';\n // Legacy scalar — the 11 types are keys in TYPE_METADATA.\n if (TYPE_METADATA[fieldType] !== undefined) return fieldType;\n return null;\n}\n\n/**\n * `true` when the schema field type is a managed-store slot - i.e. should be\n * routed through `UniqueRefStore` (or `SharedRefStore` for `'shared<T>'`)\n * for alloc / resolve / release. Derived from TYPE_METADATA[].isManaged\n * column (feat-20260611-ecs-storage-naming-ssot D-3).\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. The prefix here is\n * about column-side lifecycle ownership (the ECS releases the slot on\n * despawn / overwrite), not the retired `'managed' | 'unmanaged'` Handle\n * brand. Both `'unique<T>'` and `'shared<T>'` schema fields satisfy\n * `isManagedField` because both are ECS-tracked; the dispatcher in\n * `releaseManagedFieldOnRow` picks the right store per field type.\n */\nexport function isManagedField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isManaged ?? false;\n}\n\n/**\n * `true` when the schema field type is a managed-buffer slot - i.e. should be\n * released by the M2 BufferPool release loop. Derived from\n * TYPE_METADATA[].isBuffer column (feat-20260611-ecs-storage-naming-ssot D-3/D-4).\n *\n * D-4 semantic widening accepted: `buffer<abc>` resolves to metaKey 'buffer'\n * (isBuffer=true) while the old regex-based impl rejected the non-integer N.\n * This is a dead path — `defineComponent` rejects `buffer<abc>` via\n * `isSchemaVocabKeyword` before the predicate fires.\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. Same semantic as\n * `isManagedField` — the variable `'buffer'` keyword is one whose\n * BufferPool slot the ECS releases at despawn / overwrite time.\n */\nexport function isManagedBufferField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isBuffer ?? false;\n}\n\n/**\n * `true` when the schema field type is the single-entity reference keyword\n * `'entity'`. Derived from TYPE_METADATA[].isEntityRef column\n * (feat-20260611-ecs-storage-naming-ssot D-3).\n */\nexport function isEntityField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isEntityRef ?? false;\n}\n\n/**\n * `true` when the schema field type is an `array<T,N>` / `array<T>` vocab\n * keyword. Derived from TYPE_METADATA[].isArray column\n * (feat-20260611-ecs-storage-naming-ssot D-3).\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. Variable\n * `array<T>` storage routes through BufferPool (slot lifecycle owned by\n * the ECS); fixed `array<T,N>` is inline stride-N and has no separate\n * slot to release, but both share this predicate as they share the\n * `'array'` meta key.\n */\nexport function isManagedArrayField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isArray ?? false;\n}\n\n/**\n * Set of legal element types for the `array<T,N>` / `array<T>` keywords\n * (AC-03). Runtime mirror of `ManagedArrayElementType`.\n *\n * Naming note (D-6 whitelist): `MANAGED_ARRAY_ELEMENT_TYPES` keeps the\n * `MANAGED` prefix because `managed = ECS-tracked` here — the Set is the\n * static-whitelist arm of `isValidArrayElementType`, which gates which\n * element types the ECS array dispatch knows how to retain / release.\n * The `'shared<X>'` template family rides the `startsWith('shared<')`\n * special case (D-8) rather than living in this Set.\n */\nexport const MANAGED_ARRAY_ELEMENT_TYPES: ReadonlySet<ManagedArrayElementType> =\n new Set<ManagedArrayElementType>([\n 'f32',\n 'f64',\n 'i32',\n 'u32',\n 'i16',\n 'u16',\n 'i8',\n 'u8',\n 'bool',\n 'enum',\n 'ref',\n 'entity',\n ]);\n\n/**\n * Return `true` when `elementType` is a legal array element type\n * (static-whitelist scalar | entity, or a `shared\\<X\\>` template with a\n * non-empty tag). The empty-tag form `shared\\<\\>` is rejected\n * (plan-strategy §2 D-1 / R-NEW-1).\n *\n * @internal\n */\nfunction isValidArrayElementType(elementType: string): elementType is ManagedArrayElementType {\n if (MANAGED_ARRAY_ELEMENT_TYPES.has(elementType as ManagedArrayElementType)) return true;\n // feat-20260614 D-8: `shared<X>` is a legal element-type via the\n // startsWith special case; `MANAGED_ARRAY_ELEMENT_TYPES` Set deliberately\n // does NOT carry a `'shared'` entry (D-8 keeps the static-whitelist Set\n // free of the new family; runtime validation through the special case\n // here pairs with the independent `'shared'` TYPE_METADATA row that\n // routes element retain/release semantics in M4).\n if (elementType.startsWith('shared<') && elementType.endsWith('>') && elementType.length > 9)\n return true;\n return false;\n}\n\n/**\n * Parse an `array<T,N>` / `array<T>` schema string into its element type and\n * optional fixed length. Returns `null` if the string is not a managed-array\n * keyword or its element type is not in the whitelist (AC-03 runtime\n * fail-safe).\n *\n * Examples:\n * parseManagedArraySchema('array<entity>') => { elementType: 'entity', length: undefined }\n * parseManagedArraySchema('array<f32, 16>') => { elementType: 'f32', length: 16 }\n * parseManagedArraySchema('array<shared<MaterialAsset>>') => { elementType: 'shared<MaterialAsset>', length: undefined }\n * parseManagedArraySchema('array<shared<>>') => null (empty tag rejection)\n * parseManagedArraySchema('array<unique<X>>') => null (illegal element)\n * parseManagedArraySchema('array<array<f32,4>>') => null (nested rejected)\n *\n * Naming note (D-6 whitelist): `parseManagedArraySchema` keeps the\n * `Managed` infix because `managed = ECS-tracked` — every legal output\n * shape this parser returns is one whose lifecycle the ECS knows how to\n * retain / release on overwrite, despawn, or archetype migration.\n */\nexport function parseManagedArraySchema(\n fieldType: string,\n): { readonly elementType: ManagedArrayElementType; readonly length: number | undefined } | null {\n if (!fieldType.startsWith('array<') || !fieldType.endsWith('>')) return null;\n const inner = fieldType.slice(6, -1);\n const commaIdx = inner.indexOf(',');\n if (commaIdx === -1) {\n // Variable-capacity: inner must be a bare element-type keyword or\n // handle<X> template.\n if (!isValidArrayElementType(inner)) return null;\n return { elementType: inner as ManagedArrayElementType, length: undefined };\n }\n // Fixed-capacity: split at first comma; element-type before, integer length\n // after. Reject any further '<' / ':' / ',' to keep the form unambiguous.\n const head = inner.slice(0, commaIdx).trim();\n const tail = inner.slice(commaIdx + 1).trim();\n if (!isValidArrayElementType(head)) return null;\n if (!/^[1-9]\\d*$/.test(tail)) return null;\n return { elementType: head as ManagedArrayElementType, length: Number.parseInt(tail, 10) };\n}\n\n/**\n * Parse the byte count out of a `buffer<N>` schema keyword. Returns NaN if\n * the input does not match the keyword pattern - callers that already gated\n * via `isManagedBufferField` get a guaranteed-positive integer for the\n * fixed-byte form. The bare `'buffer'` keyword (variable byte capacity)\n * returns NaN and callers must check `fieldType === 'buffer'` separately.\n */\nexport function bufferFieldByteLength(fieldType: string): number {\n if (!fieldType.startsWith('buffer<') || !fieldType.endsWith('>')) return Number.NaN;\n const tail = fieldType.slice(7, -1);\n if (!/^[1-9]\\d*$/.test(tail)) return Number.NaN;\n return Number.parseInt(tail, 10);\n}\n\n/**\n * Runtime check for a schema-vocab keyword (the tier-2 surface).\n *\n * Pure-function regex match — kept off the hot path; only invoked by\n * `defineComponent` once per field at registration time. The match patterns\n * are the runtime mirror of `SchemaVocabKeyword` template literals.\n *\n * - `'string'` is exact-match (bare literal, no `<>`).\n * - `'buffer'` is exact-match (variable-byte capacity).\n * - `buffer<N>` requires `N` to be a positive base-10 integer (`/^[1-9]\\d*$/`).\n * Forms like `buffer<abc>` / `buffer<0>` / `buffer<>` are rejected.\n * - `unique<T>` / `shared<T>` require a non-empty target tag (`/^\\w+$/`).\n * - `entity` is exact-match.\n * - `array<T,N>` / `array<T>` accept only the whitelist element types\n * (`MANAGED_ARRAY_ELEMENT_TYPES`); illegal inner types fall through and\n * the caller surfaces `managed-array-element-type-not-allowed`.\n */\nexport function isSchemaVocabKeyword(s: string): s is SchemaVocabKeyword {\n if (s === 'string') return true;\n if (s === 'entity') return true;\n if (s === 'buffer') return true;\n if (s.startsWith('buffer<') && s.endsWith('>')) {\n const tail = s.slice(7, -1);\n return /^[1-9]\\d*$/.test(tail);\n }\n if (s.startsWith('unique<') && s.endsWith('>')) {\n return /^\\w+$/.test(s.slice(7, -1));\n }\n if (s.startsWith('shared<') && s.endsWith('>')) {\n return /^\\w+$/.test(s.slice(7, -1));\n }\n if (s.startsWith('array<') && s.endsWith('>')) {\n return parseManagedArraySchema(s) !== null;\n }\n return false;\n}\n\n/**\n * JS value-shape per managed-array element type. `entity` maps to `Entity`\n * (branded number), every scalar maps to `number` (bool is stored as a 0/1\n * byte and read back as 0 or 1).\n */\nexport type ManagedArrayElementValue<T extends ManagedArrayElementType> = T extends 'entity'\n ? EntityHandle\n : number;\n\n/**\n * Maps each field-type keyword to the JS value type read/written by it.\n *\n * Tier-1 (legacy scalars) widens to `boolean | number`; tier-2 (schema-vocab)\n * resolves to the corresponding handle / entity / buffer / array / string\n * shape via the `infer T` template-literal extraction pattern. Conditional\n * types resolve top-down --- the `'string'` arm sits BEFORE the array<...> /\n * `buffer<N>` arms so the precise literal wins template-literal resolution\n * (R-P5: prevents `'string'` from being shadowed by a wider template-literal\n * pattern). The fixed-capacity `array<T,N>` arm matches before the\n * variable-capacity `array<T>` arm by the same rule.\n *\n * The 4 buffer/array keywords (`'buffer'` / `'buffer<N>'` / `'array<T>'` /\n * `'array<T, N>'`) all resolve directly to a concrete TypedArray (or\n * Uint8Array for the byte-only buffer family). At the public `world.get`\n * boundary, a relationship-target `array<entity>` is a detached `Uint32Array`\n * snapshot. Other public array fields retain their existing transient live\n * TypedArray alias: fixed `buffer<N>` / `array<T,N>` values alias the inline\n * column buffer (feat-20260602), while variable `buffer` / `array<T>` values\n * alias the BufferPool slot bytes. Internal `readRow`, `_getArrayView`, and\n * `materializeArrayView` paths always use the live zero-copy alias. Mutation\n * flows through `world.set` / `world.push` / `world.pop`, not direct\n * assignment to a returned TypedArray.\n *\n * The `'string'` arm resolves to a native JS `string` (D-R1 / AC-13): the\n * dispatch routes the column u32 through `UniqueRefStore.resolve(handle)`\n * which returns the immutable string payload by reference.\n */\nexport type FieldValueType<T extends SchemaFieldType> = T extends 'bool'\n ? boolean\n : T extends 'entity'\n ? EntityHandle | null\n : T extends 'string'\n ? string\n : T extends 'buffer'\n ? Uint8Array\n : T extends `buffer<${number}>`\n ? Uint8Array\n : T extends `array<shared<${infer Target}>, ${number}>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<shared<${infer Target}>>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem>\n : T extends `array<${infer Elem extends ManagedArrayElementType}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem>\n : T extends `unique<${infer Target}>`\n ? Handle<Target, 'unique'>\n : T extends `shared<${infer Target}>`\n ? Handle<Target, 'shared'>\n : T extends ScalarFieldType\n ? number\n : never;\n\n/**\n * Input-side counterpart of {@link FieldValueType} for write paths\n * (`world.spawn` / `world.addComponent` / `world.set`).\n *\n * Asymmetric on `array<scalar, N>` / `array<scalar>` ONLY: the read side\n * surfaces zero-copy `Float32Array` / `Uint32Array` / etc views; the write\n * side ALSO accepts `readonly number[]` because writeArrayField copies bytes\n * verbatim from either shape (TypedArray subarray() OR per-element pack via\n * DataView). Plain literals like `times: [0.5]` reach the same code path\n * with no Float32Array wrapper boilerplate at the call site, and short\n * prefixes pad the row tail with zero (writeArrayField D-3 contract).\n *\n * Asymmetric on `buffer` / `buffer<N>`: the read side returns `Uint8Array`,\n * but the write side accepts any `AllowSharedBufferSource` (Float32Array /\n * ArrayBuffer / Uint8Array / any TypedArray). The ECS buffer-write ingestion\n * point (`World.writeRow` / `World.set`) normalizes any view to `Uint8Array`\n * over its raw bytes before storing (feat-20260621 V2 / AC-A4). This lets AI\n * users write typed param payloads directly, e.g.\n * `world.set(e, PostProcessParams, { data: Float32Array.of(exposure,0,0,0) })`,\n * without manual byte-reinterpret boilerplate at the call site.\n *\n * Every other arm matches FieldValueType verbatim (no widening): handles\n * are already arrays-of-handle, scalars stay number, etc.\n */\nexport type FieldInputType<T extends SchemaFieldType> = T extends 'bool'\n ? boolean\n : T extends 'entity'\n ? EntityHandle | null\n : T extends 'string'\n ? string\n : T extends 'buffer'\n ? AllowSharedBufferSource\n : T extends `buffer<${number}>`\n ? AllowSharedBufferSource\n : T extends `array<shared<${infer Target}>, ${number}>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<shared<${infer Target}>>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem> | readonly number[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem> | readonly number[]\n : T extends `unique<${infer Target}>`\n ? Handle<Target, 'unique'>\n : T extends `shared<${infer Target}>`\n ? Handle<Target, 'shared'>\n : T extends ScalarFieldType\n ? number\n : never;\n\n/**\n * Maps a SchemaFieldType to its zero-copy query-column view type.\n *\n * Three storage shapes share the keyword space:\n *\n * 1. Scalar / fixed-inline columns -- the column buffer is the data, written\n * in place. The bundle entry is a concrete writable TypedArray of the\n * correct ctor (`f32` -> `Float32Array`, `'buffer<N>'` -> `Uint8Array`,\n * `'array<T,N>'` -> the T-typed array). Direct index assignment is\n * fine -- the column owns the bytes.\n *\n * 2. `shared\\<X\\>` (rc-tracked AssetRegistry reference) -- the column carries\n * a u32 handle id; SharedRefStore owns the rc lifecycle. The bundle\n * entry is a `ManagedColumnReader<T>` (D-4 / D-7) -- read-only, walk\n * via `.get(i)`. Consumers route through `assets.get(handle)` to\n * materialise the asset payload.\n *\n * 3. The 4 managed-vocab keywords -- `'string'` / `` `ref<T>` `` / variable\n * `'buffer'` / variable `` `array<T>` `` -- the column carries a u32 slot\n * id; the payload lives in `UniqueRefStore` / `BufferPool`. The bundle\n * entry is a `ManagedColumnReader<T>` (D-4 / D-7) -- read-only by\n * construction, no index signature. Mutation MUST flow through the\n * public dispatch (`world.set` / `world.push` / `world.allocUniqueRef`).\n *\n * The `extends SchemaFieldType` upper bound matches `ComponentSchema[K]`\n * so query bundle types do not have to pre-filter.\n */\nexport type TypedArrayFor<T extends SchemaFieldType> = T extends 'f32'\n ? Float32Array\n : T extends 'f64'\n ? Float64Array\n : T extends 'i32'\n ? Int32Array\n : T extends 'u32' | 'enum' | 'ref' | 'entity'\n ? Uint32Array\n : T extends 'i16'\n ? Int16Array\n : T extends 'u16'\n ? Uint16Array\n : T extends 'i8'\n ? Int8Array\n : T extends 'u8' | 'bool'\n ? Uint8Array\n : T extends 'string'\n ? ManagedColumnReader<'string'>\n : T extends `unique<${string}>`\n ? ManagedColumnReader<T>\n : T extends `shared<${string}>`\n ? ManagedColumnReader<T>\n : T extends 'buffer'\n ? ManagedColumnReader<'buffer'>\n : T extends `buffer<${number}>`\n ? Uint8Array\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<\n Elem extends 'entity' | `shared<${string}>` ? 'u32' : Elem\n >\n : T extends `array<${string}>`\n ? ManagedColumnReader<T>\n : never;\n\n/**\n * Relationship metadata (feat-20260531 M2 / plan-strategy D-5). Declares this\n * component as the holder side of a Bevy-style bidirectional relationship: the\n * holder carries a single `entity` field (the target), and the engine mirrors\n * the reverse reference into `mirror`.`field` (an `array<entity>` on the target\n * entity) at add / remove / despawn time.\n *\n * - `mirror` — the mirror component's string NAME (not a type reference, so\n * `engine-ecs` never imports the mirror component type; AC-29). The mirror\n * component is a derived runtime view rebuilt by the relationship owner, so\n * it MUST declare `transient: true` — otherwise scene collect serializes it\n * and `instantiateScene` double-writes (serialized copy + owner rebuild).\n * - `field` — the `array<entity>` field on the mirror component that holds the\n * reverse list. Validated to be exactly `'array<entity>'` at `defineComponent` time.\n * - `exclusive` — when `true`, re-adding the holder component with a new target\n * auto-reparents (clears the old mirror entry, then appends the new one)\n * instead of returning `ComponentAlreadyPresentError` (AC-12).\n * - `linkedSpawn` — when `true`, despawning the target recursively despawns the\n * holders in its mirror list. Default `false` (D-1): despawn only prunes the\n * mirror entry, the holder entity survives.\n */\n/** A schema is a record of field-name → field-type keyword. */\nexport type ComponentSchema = Record<string, SchemaFieldType>;\n\n/** Derive the JS value-shape from a schema (read side; zero-copy views). */\nexport type ShapeOf<S extends ComponentSchema> = {\n [K in keyof S]: FieldValueType<S[K]>;\n};\n\n/**\n * Derive the input-side value-shape from a schema (write side; widens\n * `array<scalar>` to also accept `readonly number[]` plus the strict\n * TypedArray view). Used by `world.spawn` / `world.addComponent` /\n * `world.set` `data` so AI users can write `times: [0.5]` instead of the\n * `new Float32Array([0.5])` boilerplate. writeArrayField walks both shapes\n * via the same byte-copy path so runtime semantics are identical.\n */\nexport type InputShapeOf<S extends ComponentSchema> = {\n [K in keyof S]: FieldInputType<S[K]>;\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// ComponentId\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Numeric identity is an ECS-owner fact, not component authoring data. Keep it\n * out of the token's own enumerable surface so reflection sees only\n * `name`/`fields`/`storage`.\n */\n/** Component owner identity shared by independently bundled ECS entry points. */\nconst COMPONENT_OWNER_REGISTRY = Symbol.for('forgeax.ecs.componentOwnerRegistry');\ninterface ComponentOwnerRegistry {\n nextId: number;\n readonly ids: WeakMap<object, ComponentId>;\n readonly schemas: WeakMap<object, Readonly<Record<string, SchemaFieldType>>>;\n}\nconst ownerSymbols = globalThis as typeof globalThis & { [key: symbol]: unknown };\nconst ownerRegistry =\n (ownerSymbols[COMPONENT_OWNER_REGISTRY] as ComponentOwnerRegistry | undefined) ??\n (() => {\n const registry: ComponentOwnerRegistry = {\n nextId: 1,\n ids: new WeakMap<object, ComponentId>(),\n schemas: new WeakMap<object, Readonly<Record<string, SchemaFieldType>>>(),\n };\n ownerSymbols[COMPONENT_OWNER_REGISTRY] = registry;\n return registry;\n })();\n\nlet entityDefinitionSeen = false;\nlet componentDefinedBeforeEntity = false;\n\n/** @internal Barrel-only check for the id=0 Entity import-order invariant. */\nexport function isComponentDefinitionOrderValid(): boolean {\n return !componentDefinedBeforeEntity;\n}\n\n/** @internal Read the owner-assigned identity for storage/archetype code. */\nexport function componentId(component: Component): ComponentId {\n const id = ownerRegistry.ids.get(component);\n if (id === undefined) throw new Error(`Component identity missing for '${component.name}'.`);\n return id;\n}\n\n/** @internal Derive the flat type map from the fields SSOT. */\nexport function componentSchema<const C extends Component>(component: C): Readonly<SchemaOf<C>> {\n const schema = ownerRegistry.schemas.get(component);\n if (schema === undefined) throw new Error(`Component schema missing for '${component.name}'.`);\n return schema as Readonly<SchemaOf<C>>;\n}\n\n/** Numeric identifier for a component type, used by bitmask matching and archetype edges. */\nexport type ComponentId = number;\nexport type ComponentStorage = 'table' | 'sparse';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Token\n// ────────────────────────────────────────────────────────────────────────────\n\ndeclare const __componentBrand: unique symbol;\n\n/**\n * Opaque component token. Carries the component name `N` as a string-literal\n * type parameter (lifted from the `defineComponent` call site via `<const N>`)\n * and the schema-shape `S` as a phantom brand so `world.get(e, Comp)` can\n * return `Result<ShapeOf<S>, EcsError>` precisely.\n *\n * The `N` parameter defaults to `string` to keep existing single-parameter\n * `Component<S>` annotations source-compatible. When inferred from a\n * `defineComponent('Position', ...)` call, `N` is the literal `'Position'`,\n * which lets query row/span mapped types resolve `{ [K in N]: ... }` to a\n * concrete keyed object instead of a degraded index signature (KD-1).\n */\nexport interface Component<N extends string = string, S extends ComponentSchema = ComponentSchema> {\n readonly name: N;\n /** The one schema projection: type, default, and enum labels per field. */\n readonly fields: Readonly<Record<keyof S & string, FieldReflection>>;\n readonly storage: ComponentStorage;\n readonly [__componentBrand]: ShapeOf<S>;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// TypedArray constructors — internal; consumed by `scalarRow()` to build\n// TYPE_METADATA rows (feat-20260602 M4, w12).\n// ────────────────────────────────────────────────────────────────────────────\n\n/** TypedArray constructor for each scalar field type. */\nconst VIEW_CTORS: Readonly<\n Record<\n ScalarFieldType,\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n >\n> = {\n f32: Float32Array,\n f64: Float64Array,\n i32: Int32Array,\n u32: Uint32Array,\n i16: Int16Array,\n u16: Uint16Array,\n i8: Int8Array,\n u8: Uint8Array,\n bool: Uint8Array,\n enum: Uint32Array,\n ref: Uint32Array,\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// TYPE_METADATA — global per-type metadata table (feat-20260602 M1 / D-A6)\n//\n// Converges the 12 scattered type-intrinsic structures (3 tables + 9\n// predicate / tool functions) into a single per-type authoritative table.\n// Exports FIELD_SIZE_BYTES / VIEW_CTORS / isSchemaVocabKeyword /\n// managedArrayElementBytes / SUPPORTED_FIELD_TYPES / storageFieldType were\n// deleted M4 (w12); internal FIELD_SIZE_BYTES + VIEW_CTORS constants remain as\n// build inputs for scalarRow(). All former consumers now read TYPE_METADATA:\n// storage routing via fieldTypeToMetaKey() + TYPE_METADATA[key].storage,\n// scalar checks via TYPE_METADATA[key]?.isScalar.\n//\n// Mixed key granularity (D-5): the 11 scalars are keyed by their concrete\n// type (`f32` ... `ref`); the 6 vocab families are keyed by family (`entity`\n// / `string` / `buffer` / `ref` / `handle` / `array`). The `array` row's T/N\n// parameters are NOT table columns — they are parsed per-field into\n// `arrayMeta` (see FieldDescriptor below). The vocab `ref` family row and\n// the scalar `ref` row share the `'ref'` key intentionally: the scalar is a\n// u32 column placeholder and the vocab `ref<T>` form maps to the same\n// managed-ref storage, so one row carries both (isScalar + isManaged both\n// true). tweak-20260612-ecs-concept-compression dropped redundant columns:\n// `isVocabKeyword` (zero production consumers), the per-vocab managed-\n// ref predicate column (100% duplicate of `isManaged`), and the YAGNI\n// `fixedByteLength` placeholder;\n// `isLegacyScalar` was renamed `isScalar` (the \"legacy\" prefix labelled the\n// historical M2-introduction tense; the 11 scalars are first-class).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * One row of the global type-metadata table. Carries the type-intrinsic\n * properties a field type has regardless of which component declares it.\n *\n * - `byteSize` — element byte width for the column-storage scalar; `undefined`\n * for families whose storage byte size is not a fixed per-type constant\n * (variable buffer / array slot ids are u32-stored, surfaced via `storage`).\n * - `viewCtor` — TypedArray constructor for the column storage; `undefined`\n * for families without a direct TypedArray column.\n * - `storage` — the column-storage scalar type this field routes to (every\n * vocab family stores a u32 slot id / handle).\n * - `isScalar` — member of the 11 concrete scalar types\n * (`f32`/`f64`/`i32`/`u32`/`i16`/`u16`/`i8`/`u8`/`bool`/`enum`/`ref`).\n * - `isManaged` — routed through `UniqueRefStore` (string / ref<T>).\n * - `isBuffer` — a `buffer` / `buffer<N>` managed-byte slot.\n * - `isEntityRef` — the single-entity `entity` reference keyword.\n * - `isArray` — an `array<T>` / `array<T,N>` keyword.\n */\nexport interface TypeMetadataRow {\n readonly byteSize: number | undefined;\n readonly viewCtor:\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | undefined;\n readonly storage: ScalarFieldType;\n readonly isScalar: boolean;\n readonly isManaged: boolean;\n readonly isBuffer: boolean;\n readonly isEntityRef: boolean;\n readonly isArray: boolean;\n}\n\n/** Build a scalar row from the concrete scalar type. */\nfunction scalarRow(t: ScalarFieldType): TypeMetadataRow {\n return {\n byteSize: FIELD_SIZE_BYTES[t],\n viewCtor: VIEW_CTORS[t],\n storage: t,\n isScalar: true,\n // The scalar `ref` shares its key with the vocab `ref<T>` family; mark\n // it as managed so the single row covers both.\n isManaged: t === 'ref',\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n };\n}\n\n/**\n * Global per-type metadata table. Keyed by concrete scalar type (11) plus\n * vocab family (6 — `entity` / `string` / `buffer` / `ref` / `handle` /\n * `array`). The `ref` key is shared by the legacy scalar and the vocab family\n * (see header). Every vocab family stores a u32 slot id / handle.\n *\n * Built once at module load; frozen so downstream consumers (column.ts /\n * archetype.ts / world.ts, migrated M2) read a stable single source.\n */\nexport const TYPE_METADATA: Readonly<Record<string, TypeMetadataRow>> = Object.freeze({\n f32: scalarRow('f32'),\n f64: scalarRow('f64'),\n i32: scalarRow('i32'),\n u32: scalarRow('u32'),\n i16: scalarRow('i16'),\n u16: scalarRow('u16'),\n i8: scalarRow('i8'),\n u8: scalarRow('u8'),\n bool: scalarRow('bool'),\n enum: scalarRow('enum'),\n ref: scalarRow('ref'),\n entity: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: false,\n isEntityRef: true,\n isArray: false,\n },\n string: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: true,\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n },\n buffer: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: true,\n isEntityRef: false,\n isArray: false,\n },\n // feat-20260614-ecs-shared-component-and-unique-rename M3 (plan-strategy\n // D-3): independent `'shared'` row, NOT a reuse of the `'ref'` (post-M2:\n // `'unique<T>'` family) row. `isManaged: true` so write-barrier dispatch\n // routes shared<T> fields through release on despawn / removeComponent /\n // set-overwrite, but the M4 sub-dispatch in releaseManagedFieldOnRow will\n // separate shared (rc--) from unique (direct slot drop) using the\n // fieldType.startsWith('shared<') predicate. Keeping the meta key\n // independent preserves the \"meta key = release semantics\" invariant\n // (architecture-principles.md #1 SSOT).\n shared: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: true,\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n },\n array: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: false,\n isEntityRef: false,\n isArray: true,\n },\n});\n\n// ────────────────────────────────────────────────────────────────────────────\n// FieldDescriptor — input field-descriptor object + per-field reflection\n// (feat-20260602 M1 / D-A1 / D-A3)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Pre-parsed `array<T>` / `array<T,N>` reflection. Bare length sentinel\n * (D-A1 user ruling): `length` present => fixed-capacity, `length === undefined`\n * => variable-capacity. No `isVariable` / `kind` field — both are losslessly\n * derivable from `length` presence (architecture-principles.md #2 Derive). This\n * is exactly the existing `parseManagedArraySchema` return shape (zero shape\n * change).\n */\nexport interface ArrayMeta {\n readonly elementType: ManagedArrayElementType;\n readonly length?: number;\n}\n\n/**\n * Input field-descriptor object (D-A3). The second `defineComponent` argument\n * may declare each field either as a bare type keyword (legacy flat form,\n * still accepted through M2; migrated repo-wide in M3) or as a descriptor\n * object aggregating `type` + `default` + semantic `shape` + field-level\n * `meta`.\n *\n * - `type` — the schema field-type keyword (a parametrized string such as\n * `'array<f32,3>'` / `'unique<MaterialAsset>'` is used verbatim, D-A2).\n * - `default` — layer-2 default value; retained in the field reflection row.\n * - `shape` — producer-owned semantic shape tag for schema consumers; it does\n * not change ECS storage or runtime value semantics.\n * - `meta` — field-level open namespace; aggregated into `component.meta`. The\n * infra gives no key special meaning (open map, OOS-1).\n * - `transient` — when `true`, scene collect skips this field (D-5). Same word,\n * same meaning as the component-level `transient` flag, with granularity sunk\n * to the field level: a field that is derived/reconstructable (e.g. a resolved\n * world mat4) is excluded from serialization while its component's persisted\n * fields still round-trip. Absent (the common case) means the field is\n * serialized.\n * - `labels` — for an `enum` field ONLY: the label→numeric-value map (e.g.\n * `{ static: 0, dynamic: 1, kinematic: 2 }`). An `enum` field stores a bare\n * `u32` variant index; the human-readable names historically lived in a\n * SEPARATE per-package const map (`RigidBodyTypeValue`) + comment table, so no\n * schema consumer could read them and the two could drift. Declaring `labels`\n * attaches that map to the field itself (SSOT-adjacent): it is aggregated into\n * `component.fields[field].labels` and surfaced by reflection consumers (the\n * editor's `describeComponent`, inspector UIs, validation hints) so a\n * docs-only user learns the legal variants + their integers from the schema\n * alone. Pass the EXISTING `*Value` const map here (Derive, don't Duplicate —\n * one object, two consumers). Absent for non-enum fields / enums that opt out.\n */\nexport interface FieldDescriptor<T extends SchemaFieldType = SchemaFieldType> {\n readonly type: T;\n readonly default?: FieldValueType<T>;\n /** Semantic authoring shape; storage still follows `type`. */\n readonly shape?: FieldShapeKind;\n readonly meta?: Readonly<Record<string, unknown>>;\n readonly transient?: boolean;\n readonly labels?: Readonly<Record<string, number>>;\n}\n\n/**\n * Per-field reflection produced at registration time and read off\n * `component.fields[fieldName]` (D-A3). Carries the pre-parsed facts: the\n * field `type`, its `default` (if any), semantic `shape` (if declared), — for\n * `array<...>` fields only — the pre-parsed `arrayMeta` (parse happens once at\n * registration, AC-03c), and the field-level `transient` flag (D-5) when\n * declared.\n *\n * `transient` mirrors the component-level `Component.transient` (same word,\n * same meaning): scene collect skips a `transient` field just as it skips a\n * `transient` component. Granularity is sunk to the field level so a component\n * can persist most of its fields while excluding a derived/reconstructable one\n * (e.g. `GlobalTransform.world`). Absent means the field participates in\n * serialization.\n */\nexport interface FieldReflection {\n readonly type: SchemaFieldType;\n readonly default?: unknown;\n /** Producer-declared semantic shape, when storage type alone is insufficient. */\n readonly shape?: FieldShapeKind;\n readonly arrayMeta?: ArrayMeta;\n readonly transient?: boolean;\n /**\n * For an `enum` field: the label→numeric-value map declared on the field\n * descriptor (see `FieldDescriptor.labels`). Lets a schema consumer resolve a\n * variant name ↔ its stored `u32` index without a separate const map. Absent\n * for non-enum fields / enums that did not declare labels.\n */\n readonly labels?: Readonly<Record<string, number>>;\n}\n\n/**\n * One input field-spec value: either the bare type keyword (legacy flat form)\n * or a field-descriptor object. Accepting both keeps the ~44 flat-string\n * call-sites + ~55 test files green through M1/M2 while the field-descriptor\n * form is migrated in repo-wide in M3 (D-A7 / D-A8 shrink the migration\n * surface to the input side only).\n */\nexport type FieldSpec<T extends SchemaFieldType = SchemaFieldType> = T | FieldDescriptor<T>;\n\n/** An input field-spec map: field-name -> bare keyword | field-descriptor. */\nexport type FieldsInput = Record<string, FieldSpec>;\n\n/**\n * Project an input field-spec map down to its flat `ComponentSchema` shape\n * (field-name -> type keyword). A bare-keyword spec maps to itself (identity,\n * so existing flat-string call-sites infer exactly as before); a descriptor\n * spec maps to its `type`. This keeps `Component<N, SchemaOf<F>>` driving every\n * downstream type (ShapeOf / query row/span projection / TypedArrayFor) unchanged.\n */\nexport type SchemaOf<F extends FieldsInput | Component> =\n F extends Component<string, infer S>\n ? S\n : F extends FieldsInput\n ? {\n [K in keyof F]: F[K] extends FieldDescriptor<infer T>\n ? T\n : F[K] extends SchemaFieldType\n ? F[K]\n : never;\n }\n : never;\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineComponent\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Optional configuration for `defineComponent` (w4, M3 consumer; w21 layer-2 defaults). */\nexport interface DefineComponentOptions {\n readonly storage?: ComponentStorage;\n /**\n * When `true`, the component is skipped by scene collect\n * (rootsToSceneAsset). The component stays in archetype columns and\n * participates normally in queries / world.get at runtime.\n *\n * Default: `false`. Mirror targets of relationship components should\n * declare `transient: true` so they are not serialized (their state is\n * rebuilt by the mirror hook after instantiateScene).\n */\n readonly transient?: boolean;\n /**\n * Components materialized automatically when this component is added.\n * Explicit data for a required component wins; the ECS appends only missing\n * identities at the spawn/add boundary, never from a frame system.\n */\n readonly requires?: readonly Component[];\n /**\n * Component-level open metadata namespace. Entries are copied into\n * `Component.meta` at registration; the ECS core assigns no meaning to any\n * key. Component-level entries win over field-level entries with the same\n * key, and consumers may extend the mutable map after registration.\n */\n readonly meta?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Extract the bare field-type keyword from a field-spec (bare keyword | field-\n * descriptor object), fail-fast if a descriptor object is missing its `type`.\n * The throw carries the field name + expected shape (charter P3 / OOS-6: this\n * is a programmer error caught at registration time, no new EcsErrorCode).\n */\nfunction fieldSpecType(fieldName: string, spec: FieldSpec): SchemaFieldType {\n if (typeof spec === 'string') return spec as SchemaFieldType;\n const t = (spec as FieldDescriptor).type;\n if (typeof t !== 'string') {\n throw new SchemaUnsupportedFieldError(\n fieldName,\n `<field-descriptor missing 'type'> (expected { type, default?, meta? })`,\n );\n }\n return t as SchemaFieldType;\n}\n\n/**\n * Define a component. Returns a frozen opaque token with exactly three runtime\n * facts: `.name`, `.fields`, and `.storage`. Numeric identity, flat schema,\n * and defaults are owner projections held outside the token.\n *\n * The second argument accepts each field either as a bare type keyword\n * ('f32', 'array<entity>', ...) or as a field-descriptor object\n * `{ type, default?, meta? }` (D-A3). The single `FieldsInput` overload\n * handles both forms — bare keywords are identity through `SchemaOf<F>`.\n *\n * The `<const N>` modifier lifts `name` to its string-literal type so the\n * returned `Component<N, SchemaOf<S>>` drives precise key-based mapped types\n * downstream (for example QueryRow and QuerySpan projections). At runtime `name` is a plain\n * string.\n *\n * Schema-field validation accepts both the legacy scalar tier\n * (`ScalarFieldType`, 11 keywords) and the schema-vocab tier\n * (`SchemaVocabKeyword`, 8 patterns including `array<T,N>` / `array<T>` /\n * `buffer` / `buffer<N>`). Mismatched values raise\n * `SchemaUnsupportedFieldError`. Illegal `array<...>` element types\n * (e.g. `array<ref<X>>`) raise `ManagedArrayElementTypeNotAllowedError`\n * (AC-03 runtime fail-safe).\n *\n * Relationship roles are declared through `defineRelationship`; component\n * definitions contain only schema vocabulary and no mirror metadata.\n *\n * @throws SchemaUnsupportedFieldError for any field type not in the supported\n * set, or a field-descriptor object missing its `type`.\n * @throws ManagedArrayElementTypeNotAllowedError when an `array<...>`\n * keyword carries an illegal element type.\n * @throws RelationshipMirrorComponentNotRegisteredError when\n * `relationship.mirror` names a component not yet defined.\n * @throws RelationshipMirrorFieldTypeMismatchError when the mirror's\n * `relationship.field` is missing or not typed `'array<entity>'`.\n */\n// Single signature post-M4 (w12) — bare-keyword field specs are valid\n// FieldSpec<T> values (identity through SchemaOf<F>), so flat-string schemas\n// work without a separate overload. tweak-20260612-ecs-concept-compression\n// dropped the redundant byte-identical overload declaration.\nexport function defineComponent<const N extends string, const S extends FieldsInput>(\n name: N,\n fields: S,\n options?: DefineComponentOptions,\n): Component<N, SchemaOf<S>> {\n const storage = options?.storage ?? 'table';\n assertComponentStorage(storage);\n if (storage === 'sparse' && Object.keys(fields).length !== 0) {\n throw new SparseStorageRequiresTagError(name);\n }\n const schema: Record<string, SchemaFieldType> = {};\n const reflectedFields: Record<string, FieldReflection> = {};\n const collectedMeta: Record<string, unknown> = {};\n const collectedDefaults: Record<string, unknown> = {};\n\n for (const fieldName of Object.keys(fields)) {\n const spec = fields[fieldName] as FieldSpec;\n const fieldType = fieldSpecType(fieldName, spec);\n\n // Validate the field type — same fail-fast as before, now over the\n // normalized keyword.\n let arrayMeta: ArrayMeta | undefined;\n if (TYPE_METADATA[fieldType]?.isScalar === true) {\n // legacy scalar — ok\n } else if (fieldType === 'string') {\n // string vocab — ok\n } else if (fieldType.startsWith('array<') && fieldType.endsWith('>')) {\n const parsed = parseManagedArraySchema(fieldType);\n if (parsed === null) {\n const elementType = fieldType.slice(6, -1);\n throw new ManagedArrayElementTypeNotAllowedError(fieldName, elementType);\n }\n // Pre-parse once at registration (AC-03c): array fields cache arrayMeta.\n // Bare length sentinel {elementType, length?} (D-A1): drop `length` when\n // variable so the row is byte-identical to the parse return shape.\n arrayMeta = deepFreeze(\n parsed.length === undefined\n ? { elementType: parsed.elementType }\n : { elementType: parsed.elementType, length: parsed.length },\n );\n } else if (!isSchemaVocabKeyword(fieldType)) {\n throw new SchemaUnsupportedFieldError(fieldName, fieldType);\n }\n\n schema[fieldName] = fieldType;\n\n // Per-field reflection row — only attach arrayMeta / default when present\n // (exactOptionalPropertyTypes: never set an explicit `undefined`).\n const row: {\n type: string;\n default?: unknown;\n shape?: FieldShapeKind;\n arrayMeta?: ArrayMeta;\n transient?: boolean;\n labels?: Readonly<Record<string, number>>;\n } = {\n type: fieldType,\n };\n if (typeof spec !== 'string') {\n const desc = spec as FieldDescriptor;\n if ('default' in desc) {\n row.default = desc.default;\n collectedDefaults[fieldName] = desc.default;\n }\n if (desc.shape !== undefined) row.shape = desc.shape;\n if (desc.meta !== undefined) {\n Object.assign(collectedMeta, desc.meta);\n }\n // exactOptionalPropertyTypes: only attach `transient` when declared.\n if (desc.transient !== undefined) row.transient = desc.transient;\n // enum label→value map (see FieldDescriptor.labels). Frozen so the\n // reflected row exposes a stable read-only map. Only enum fields declare it.\n if (desc.labels !== undefined) row.labels = deepFreeze({ ...desc.labels });\n }\n if (arrayMeta !== undefined) row.arrayMeta = arrayMeta;\n reflectedFields[fieldName] = Object.freeze(row) as FieldReflection;\n }\n\n if (name === 'Entity') {\n entityDefinitionSeen = true;\n } else if (!entityDefinitionSeen) {\n componentDefinedBeforeEntity = true;\n }\n const id = name === 'Entity' ? 0 : ownerRegistry.nextId++;\n\n // Derived defaults projection (D-A8): pure from `fields[k].default`.\n // No longer merged with a removed `options.defaults` input — strict single-entry\n // means the only way to set a layer-2 default is through the field descriptor.\n const frozenDefaults =\n Object.keys(collectedDefaults).length === 0\n ? undefined\n : (deepFreeze(collectedDefaults) as Readonly<Partial<ShapeOf<SchemaOf<S>>>>);\n\n // Keep the component token immutable while leaving its open metadata map\n // extensible for higher-level consumers after registration.\n if (options?.meta !== undefined) {\n Object.assign(collectedMeta, options.meta);\n }\n const frozenSchema = deepFreeze(schema);\n const frozenFields = deepFreeze(reflectedFields);\n const meta = collectedMeta;\n const token = Object.freeze({ name, fields: frozenFields, storage }) as Component<N, SchemaOf<S>>;\n ownerRegistry.ids.set(token, id);\n ownerRegistry.schemas.set(token, frozenSchema);\n registerComponentDefinition(token, {\n fields: frozenFields,\n defaults: frozenDefaults,\n policy: {\n transient: options?.transient ?? false,\n meta,\n requires: Object.freeze([...(options?.requires ?? [])]),\n },\n });\n return Object.freeze(token);\n}\n\nexport class ComponentInUseError extends Error {\n override readonly name = 'ComponentInUseError';\n readonly code = 'component-in-use' as const;\n readonly expected = 'the component to have no live entity or scheduled-system references';\n readonly hint =\n 'Remove owning systems and component values before disposing the registration lease.';\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n super(`Component ${componentName} is still in use.`);\n this.detail = { componentName };\n }\n}\n\nexport class ComponentNameConflictError extends Error {\n override readonly name = 'ComponentNameConflictError';\n readonly code = 'component-name-conflict' as const;\n readonly expected = 'one component token per name in a World';\n readonly hint =\n 'Use the token already registered in this World or choose a distinct component name.';\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n super(`Component ${componentName} is already registered with a different token.`);\n this.detail = { componentName };\n }\n}\n\nexport type ComponentCatalogError = ComponentInUseError | ComponentNameConflictError;\n\nexport interface ComponentLease {\n readonly component: Component;\n dispose(): Result<void, ComponentInUseError>;\n}\n\ninterface ComponentRegistration {\n readonly component: Component;\n owners: number;\n}\n\n/** World-local discovery and ownership boundary for plugin-installed component vocabulary. */\nexport class ComponentCatalog {\n private readonly registrations = new Map<string, ComponentRegistration>();\n\n constructor(private readonly inUse: (component: Component) => boolean) {}\n\n register(component: Component): Result<ComponentLease, ComponentNameConflictError> {\n const current = this.registrations.get(component.name);\n if (current !== undefined && current.component !== component) {\n return err(new ComponentNameConflictError(component.name));\n }\n if (current === undefined) {\n this.registrations.set(component.name, { component, owners: 1 });\n } else {\n current.owners += 1;\n }\n\n let active = true;\n return ok({\n component,\n dispose: () => {\n if (!active) return ok(undefined);\n const registration = this.registrations.get(component.name);\n if (registration === undefined || registration.component !== component) {\n active = false;\n return ok(undefined);\n }\n if (registration.owners > 1) {\n registration.owners -= 1;\n active = false;\n return ok(undefined);\n }\n if (this.inUse(component)) return err(new ComponentInUseError(component.name));\n this.registrations.delete(component.name);\n active = false;\n return ok(undefined);\n },\n });\n }\n\n resolve(name: string): Component | undefined {\n return this.registrations.get(name)?.component;\n }\n\n entries(): ReadonlyMap<string, Component> {\n return new Map(\n [...this.registrations].map(([name, registration]) => [name, registration.component]),\n );\n }\n}\n","// @forgeax/engine-ecs/internal — package-owner seams that are intentionally\n// absent from the public ECS barrel.\n//\n// This entry keeps the existing component-owner imports stable and adds the\n// typed symbol used by scene propagation. A symbol avoids putting a derived\n// writer method on the discoverable Query contract while still letting the\n// owning package share the exact query implementation without a private path\n// import or an untyped cast at each consumer.\n\nexport * from './component';\nexport type { WorldInternal } from './world-internal';\nexport { worldInternal } from './world-internal';\n\nimport type { Result } from '@forgeax/engine-types';\nimport type { Component } from './component';\nimport type { QuerySpanUnavailableError } from './errors';\nimport type { DerivedRangeWriter } from './query/derived-range-writer';\nimport type { Query } from './query/query';\n\n/** Internal identity for the ECS-owned derived range writer accessor. */\nexport const DERIVED_WRITER: unique symbol = Symbol.for(\n 'forgeax.ecs.query.derivedWriter',\n) as unknown as typeof DERIVED_WRITER;\n\ntype DerivedWriterQuery<\n R extends readonly Component[],\n W extends readonly Component[],\n O extends readonly Component[],\n> = Query<R, W, O> & {\n readonly [DERIVED_WRITER]: <C extends W[number]>(\n component: C,\n ) => Result<DerivedRangeWriter<R[number], C>, QuerySpanUnavailableError>;\n};\n\n/**\n * Resolve the ECS-owned derived writer through its internal symbol seam.\n * Consumers of this module retain the component/query type relationship while\n * the public Query and QuerySpan surfaces remain read/write-only contracts.\n */\nexport function getDerivedWriter<\n R extends readonly Component[],\n W extends readonly Component[],\n O extends readonly Component[],\n C extends W[number],\n>(\n query: Query<R, W, O>,\n component: C,\n): Result<DerivedRangeWriter<R[number], C>, QuerySpanUnavailableError> {\n const internalQuery = query as DerivedWriterQuery<R, W, O>;\n return internalQuery[DERIVED_WRITER](component);\n}\n\nexport type {\n DerivedColumnBinding,\n DerivedRangeCursor,\n DerivedRangeRowCommit,\n DerivedRangeRowProbe,\n DerivedRangeWriter,\n} from './query/derived-range-writer';\n"]}
1
+ {"version":3,"sources":["../src/component-schema.ts","../src/component.ts","../src/internal.ts"],"names":[],"mappings":"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","file":"internal.mjs","sourcesContent":["import type { Component, FieldReflection, SchemaFieldType } from './component';\n\n/** The complete storage placement vocabulary owned by ECS schema. */\nexport type ComponentStorageKind = 'table' | 'sparse';\n\n/** The stable public facts needed to describe a component schema offline. */\nexport interface ComponentSchemaDefinition {\n readonly name: string;\n readonly fields: Readonly<Record<string, SchemaFieldType>>;\n readonly storage: ComponentStorageKind;\n}\n\n/**\n * Serialization facts owned beside schema registration, not carried by the\n * public component token. Queries and storage only need the token's schema\n * vocabulary; domain validation and lifecycle belong to their owners.\n */\nexport interface ComponentPolicy {\n readonly transient: boolean;\n readonly meta: Record<string, unknown>;\n /** Components that are materialized whenever this component is added. */\n readonly requires: readonly Component[];\n}\n\n/** Immutable schema reflection plus non-token policy projection. */\nexport interface ComponentDefinition {\n readonly fields: Readonly<Record<string, FieldReflection>>;\n readonly defaults: Readonly<Record<string, unknown>> | undefined;\n readonly policy: ComponentPolicy;\n}\n\n// Component tokens can cross independently bundled ECS entry points. Share\n// the owner registry through globalThis rather than attaching a hidden symbol\n// to each token; `Reflect.ownKeys(token)` must remain exactly the three public\n// facts name/fields/storage.\nconst COMPONENT_REGISTRY = Symbol.for('forgeax.ecs.componentRegistry');\ninterface ComponentRegistry {\n readonly definitions: WeakMap<object, ComponentDefinition>;\n}\nconst globalSymbols = globalThis as typeof globalThis & { [key: symbol]: unknown };\nconst definitions =\n (globalSymbols[COMPONENT_REGISTRY] as ComponentRegistry | undefined) ??\n (() => {\n const registry: ComponentRegistry = { definitions: new WeakMap<object, ComponentDefinition>() };\n globalSymbols[COMPONENT_REGISTRY] = registry;\n return registry;\n })();\n\nexport function registerComponentDefinition(\n component: Component,\n definition: ComponentDefinition,\n): void {\n definitions.definitions.set(component, definition);\n}\n\n/** Read the definition-time schema/policy projection for a component token. */\nexport function componentDefinition(component: Component): ComponentDefinition {\n const definition = definitions.definitions.get(component);\n if (definition === undefined) {\n throw new Error(`Component definition missing for '${component.name}'.`);\n }\n return definition;\n}\n\n/** Read the generic structural requirements declared by one component. */\nexport function componentRequirements(component: Component): readonly Component[] {\n // Keep invalid/foreign tokens on the ordinary preflight error path. The\n // expansion helper runs before validation, so it must not turn a structured\n // `component-not-defined` Result into an uncaught registry exception.\n return definitions.definitions.get(component)?.policy.requires ?? [];\n}\n\ntype ComponentDataLike = {\n readonly component: Component;\n readonly data: Partial<Record<string, unknown>>;\n};\n\n/**\n * Expand component requirements once at the structural boundary.\n *\n * Explicit component data wins and is never duplicated. Requirements are\n * appended in declaration order, and the same identity set also terminates a\n * malformed dependency cycle without a per-frame scan.\n */\nexport function expandComponentRequirements<T extends ComponentDataLike>(\n componentDatas: readonly T[],\n): T[] {\n let hasRequirements = false;\n for (const entry of componentDatas) {\n if (componentRequirements(entry.component).length !== 0) {\n hasRequirements = true;\n break;\n }\n }\n // Most structural operations use components without dependencies. Preserve\n // that path without copying or allocating a Set; callers only consume the\n // returned list and never mutate it.\n if (!hasRequirements) return componentDatas as T[];\n\n const expanded = [...componentDatas];\n const seen = new Set<Component>(expanded.map((entry) => entry.component));\n for (let index = 0; index < expanded.length; index++) {\n const component = expanded[index]?.component;\n if (component === undefined) continue;\n for (const required of componentRequirements(component)) {\n if (seen.has(required)) continue;\n seen.add(required);\n expanded.push({ component: required, data: {} } as T);\n }\n }\n return expanded;\n}\n\n/**\n * Freeze a schema value recursively. Component descriptors are authored data,\n * so nested defaults and enum label maps must not become mutation channels.\n */\nexport function deepFreeze<T>(value: T): Readonly<T> {\n if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {\n return value as Readonly<T>;\n }\n // Non-empty TypedArrays reject Object.freeze and their indexed elements\n // remain writable even when the wrapper is frozen. Treat binary views as\n // immutable leaf projections; the authored descriptor around them is still\n // recursively frozen by this function.\n if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {\n return value as Readonly<T>;\n }\n const object = value as object;\n for (const key of Reflect.ownKeys(object)) {\n const child = (object as Record<PropertyKey, unknown>)[key];\n if (child !== null && (typeof child === 'object' || typeof child === 'function')) {\n deepFreeze(child);\n }\n }\n return Object.freeze(value) as Readonly<T>;\n}\n\nexport function assertComponentStorage(value: string): asserts value is ComponentStorageKind {\n if (value !== 'table' && value !== 'sparse') {\n throw new Error(`Unsupported component storage '${value}'. Expected 'table' or 'sparse'.`);\n }\n}\n","// @forgeax/engine-ecs — Component schema + opaque token.\n//\n// `defineComponent(name, fields, options?)` returns a frozen token carrying\n// only the three runtime facts needed by callers:\n// - `.name`: component name string\n// - `.fields`: frozen field descriptors (the schema SSOT)\n// - `.storage`: table or sparse placement\n// Numeric identity, flat schema projections, and default maps live in the ECS\n// owner tables below rather than on the public token.\n//\n// ComponentId is used by archetype storage, bitmask matching, and edges cache.\n\nimport { err, type Handle, ok, type Result } from '@forgeax/engine-types';\nimport {\n assertComponentStorage,\n deepFreeze,\n registerComponentDefinition,\n} from './component-schema';\nimport type { EntityHandle } from './entity-handle';\nimport {\n ManagedArrayElementTypeNotAllowedError,\n SchemaUnsupportedFieldError,\n SparseStorageRequiresTagError,\n} from './errors';\nimport type { ManagedColumnReader } from './storage/column';\n\n// The internal package subpath reuses this owner module so the source budget\n// does not grow a second forwarding module. Root exports remain curated in\n// index.ts; this re-export is only reached through `@forgeax/engine-ecs/internal`.\nexport { componentDefinition } from './component-schema';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Field types — schema vocab keywords (AC-01).\n//\n// Two-tier vocabulary:\n//\n// 1. Legacy scalar set: 11 keywords backed by TypedArray storage. Concrete\n// byte-sizes + TypedArray constructors are internal constants consumed by\n// `scalarRow()` to build TYPE_METADATA rows (see M4 §FIELD_SIZE_BYTES / VIEW_CTORS).\n//\n// 2. Schema-vocab keywords: 7 template-literal patterns expressing\n// ECS-managed types whose storage is owned by separate subsystems:\n// * `buffer:<bytes>` — fixed-byte managed Uint8Array, stored by BufferPool\n// * `ref<T>` — managed Handle<T,'unique'>, released by UniqueRefStore\n// * `shared<T>` — rc-tracked Handle<T,'shared'>, lifecycle owned by SharedRefStore\n// * `entity` — Entity reference (Entity | null)\n// * `string` — utf-8 string payload, allocated as a managed handle via UniqueRefStore\n// * `array<T,N>` — fixed-capacity typed view; elements inline in stride-N column (feat-20260602)\n// * `array<T>` — variable-capacity typed view over BufferPool slot bytes\n//\n// The retired `array<entity>` predecessor (closed out by this feat) is no\n// longer a valid schema field type — the union has narrowed it out.\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Bytes per element for each scalar field type. */\nconst FIELD_SIZE_BYTES = {\n f32: 4,\n f64: 8,\n i32: 4,\n u32: 4,\n i16: 2,\n u16: 2,\n i8: 1,\n u8: 1,\n bool: 1,\n enum: 4,\n ref: 4,\n} as const;\n\n/** Numeric scalar field types backed by TypedArray storage (legacy tier). */\nexport type ScalarFieldType = keyof typeof FIELD_SIZE_BYTES;\n\n/**\n * Legal element-type whitelist for the `array<T,N>` / `array<T>` vocab\n * keywords (AC-03). T must be a scalar field type, `entity`, or a\n * `shared\\<X\\>` template with a non-empty tag; reference / buffer / nested\n * array element types are forbidden (OOS-08 / OOS-03).\n *\n * feat-20260614 M5 / w23: the historical `handle\\<X\\>` element family was\n * deleted in favor of `shared\\<X\\>` (rc-tracked, lifecycle owned by\n * SharedRefStore). The `MANAGED_ARRAY_ELEMENT_TYPES` Set remains\n * static-scalar + entity only (D-8); dynamic `shared\\<X\\>` templates are\n * validated by `isValidArrayElementType` at parse time.\n *\n * Legal: every member of `ScalarFieldType` plus `entity` plus\n * `shared\\<X\\>` (non-empty tag). The `ref` legacy scalar keyword (a u32\n * column placeholder) is in the whitelist; the parametric `unique<T>` /\n * `shared<T>` scalars are rejected as array element types by AC-03.\n */\nexport type ManagedArrayElementType = ScalarFieldType | 'entity' | `shared<${string}>`;\n\n/**\n * Schema-vocab keywords beyond the legacy scalar tier (AC-01).\n *\n * Each pattern is a template-literal type so a literal schema like\n * `{ mat: 'unique<MaterialAsset>' }` types the value as\n * `Handle<'MaterialAsset','unique'>` end-to-end. Runtime acceptance is\n * gated by the internal `isSchemaVocabKeyword` check — the SSOT for parser fail-fast.\n *\n * The legacy `'buffer:<N>'` literal is retired one-cut by\n * feat-20260515-buffer-array-vocab-collapse w4: replaced by the\n * angle-bracket generic shapes `'buffer'` (variable byte slot) and\n * `'buffer<N>'` (fixed byte slot). With `'array<T>'` / `'array<T, N>'` they\n * form a 4-keyword closed surface across two orthogonal axes (element-type\n * x capacity contract).\n */\nexport type SchemaVocabKeyword =\n | 'string'\n | 'buffer'\n | `buffer<${number}>`\n | `unique<${string}>`\n | `shared<${string}>`\n | 'entity'\n | `array<${ManagedArrayElementType}, ${number}>`\n | `array<${ManagedArrayElementType}>`;\n\n/**\n * Closed union of every keyword `defineComponent` accepts for a schema field.\n * Combines the legacy scalar tier with the schema-vocab tier.\n *\n * `ComponentSchema` is keyed against this union; `defineComponent` rejects\n * any field value not satisfying it (compile-time) or matching it\n * (runtime).\n */\nexport type SchemaFieldType = ScalarFieldType | SchemaVocabKeyword;\n\n/**\n * Producer-owned semantic shape tags for authoring/schema consumers.\n *\n * The ECS storage vocabulary remains the source of truth for bytes and\n * runtime values. These tags capture the semantic shape that storage alone\n * cannot express (for example an optional entity reference or a nested\n * unique payload). The tag is deliberately closed so downstream consumers\n * can exhaustively handle the representative field-shape vocabulary without\n * creating a second component registry.\n */\nexport type FieldShapeKind =\n | 'scalar'\n | 'boolean'\n | 'enum'\n | 'vector'\n | 'quaternion'\n | 'optional'\n | 'nested'\n | 'array'\n | 'asset-ref';\n\n/**\n * Normalize any field-type keyword to its TYPE_METADATA key.\n *\n * The 11 legacy scalars round-trip their own key. The 6 vocab families normalize\n * their parametric shapes to the family key:\n * - `unique<T>` / `shared<T>` — strip `<T>` → `'ref'` / `'shared'`\n * - `buffer<N>` — strip `<N>` → `'buffer'`\n * - `array<T>` / `array<T,N>` — strip `<...>` → `'array'`\n * - `entity` / `string` / `buffer` are identity.\n *\n * Returns `null` for an unrecognised keyword so callers can skip column\n * allocation (same semantics as the retired `storageFieldType`).\n */\nexport function fieldTypeToMetaKey(fieldType: string): string | null {\n if (fieldType === 'entity' || fieldType === 'string' || fieldType === 'buffer') {\n return fieldType;\n }\n if (fieldType.startsWith('unique<') && fieldType.endsWith('>')) return 'ref';\n if (fieldType.startsWith('shared<') && fieldType.endsWith('>')) return 'shared';\n if (fieldType.startsWith('buffer<') && fieldType.endsWith('>')) return 'buffer';\n if (fieldType.startsWith('array<') && fieldType.endsWith('>')) return 'array';\n // Legacy scalar — the 11 types are keys in TYPE_METADATA.\n if (TYPE_METADATA[fieldType] !== undefined) return fieldType;\n return null;\n}\n\n/**\n * `true` when the schema field type is a managed-store slot - i.e. should be\n * routed through `UniqueRefStore` (or `SharedRefStore` for `'shared<T>'`)\n * for alloc / resolve / release. Derived from TYPE_METADATA[].isManaged\n * column (feat-20260611-ecs-storage-naming-ssot D-3).\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. The prefix here is\n * about column-side lifecycle ownership (the ECS releases the slot on\n * despawn / overwrite), not the retired `'managed' | 'unmanaged'` Handle\n * brand. Both `'unique<T>'` and `'shared<T>'` schema fields satisfy\n * `isManagedField` because both are ECS-tracked; the dispatcher in\n * `releaseManagedFieldOnRow` picks the right store per field type.\n */\nexport function isManagedField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isManaged ?? false;\n}\n\n/**\n * `true` when the schema field type is a managed-buffer slot - i.e. should be\n * released by the M2 BufferPool release loop. Derived from\n * TYPE_METADATA[].isBuffer column (feat-20260611-ecs-storage-naming-ssot D-3/D-4).\n *\n * D-4 semantic widening accepted: `buffer<abc>` resolves to metaKey 'buffer'\n * (isBuffer=true) while the old regex-based impl rejected the non-integer N.\n * This is a dead path — `defineComponent` rejects `buffer<abc>` via\n * `isSchemaVocabKeyword` before the predicate fires.\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. Same semantic as\n * `isManagedField` — the variable `'buffer'` keyword is one whose\n * BufferPool slot the ECS releases at despawn / overwrite time.\n */\nexport function isManagedBufferField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isBuffer ?? false;\n}\n\n/**\n * `true` when the schema field type is the single-entity reference keyword\n * `'entity'`. Derived from TYPE_METADATA[].isEntityRef column\n * (feat-20260611-ecs-storage-naming-ssot D-3).\n */\nexport function isEntityField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isEntityRef ?? false;\n}\n\n/**\n * `true` when the schema field type is an `array<T,N>` / `array<T>` vocab\n * keyword. Derived from TYPE_METADATA[].isArray column\n * (feat-20260611-ecs-storage-naming-ssot D-3).\n *\n * Naming note (D-6 whitelist): `managed = ECS-tracked`. Variable\n * `array<T>` storage routes through BufferPool (slot lifecycle owned by\n * the ECS); fixed `array<T,N>` is inline stride-N and has no separate\n * slot to release, but both share this predicate as they share the\n * `'array'` meta key.\n */\nexport function isManagedArrayField(fieldType: string): boolean {\n return TYPE_METADATA[fieldTypeToMetaKey(fieldType) ?? '']?.isArray ?? false;\n}\n\n/**\n * Set of legal element types for the `array<T,N>` / `array<T>` keywords\n * (AC-03). Runtime mirror of `ManagedArrayElementType`.\n *\n * Naming note (D-6 whitelist): `MANAGED_ARRAY_ELEMENT_TYPES` keeps the\n * `MANAGED` prefix because `managed = ECS-tracked` here — the Set is the\n * static-whitelist arm of `isValidArrayElementType`, which gates which\n * element types the ECS array dispatch knows how to retain / release.\n * The `'shared<X>'` template family rides the `startsWith('shared<')`\n * special case (D-8) rather than living in this Set.\n */\nexport const MANAGED_ARRAY_ELEMENT_TYPES: ReadonlySet<ManagedArrayElementType> =\n new Set<ManagedArrayElementType>([\n 'f32',\n 'f64',\n 'i32',\n 'u32',\n 'i16',\n 'u16',\n 'i8',\n 'u8',\n 'bool',\n 'enum',\n 'ref',\n 'entity',\n ]);\n\n/**\n * Return `true` when `elementType` is a legal array element type\n * (static-whitelist scalar | entity, or a `shared\\<X\\>` template with a\n * non-empty tag). The empty-tag form `shared\\<\\>` is rejected\n * (plan-strategy §2 D-1 / R-NEW-1).\n *\n * @internal\n */\nfunction isValidArrayElementType(elementType: string): elementType is ManagedArrayElementType {\n if (MANAGED_ARRAY_ELEMENT_TYPES.has(elementType as ManagedArrayElementType)) return true;\n // feat-20260614 D-8: `shared<X>` is a legal element-type via the\n // startsWith special case; `MANAGED_ARRAY_ELEMENT_TYPES` Set deliberately\n // does NOT carry a `'shared'` entry (D-8 keeps the static-whitelist Set\n // free of the new family; runtime validation through the special case\n // here pairs with the independent `'shared'` TYPE_METADATA row that\n // routes element retain/release semantics in M4).\n if (elementType.startsWith('shared<') && elementType.endsWith('>') && elementType.length > 9)\n return true;\n return false;\n}\n\n/**\n * Parse an `array<T,N>` / `array<T>` schema string into its element type and\n * optional fixed length. Returns `null` if the string is not a managed-array\n * keyword or its element type is not in the whitelist (AC-03 runtime\n * fail-safe).\n *\n * Examples:\n * parseManagedArraySchema('array<entity>') => { elementType: 'entity', length: undefined }\n * parseManagedArraySchema('array<f32, 16>') => { elementType: 'f32', length: 16 }\n * parseManagedArraySchema('array<shared<MaterialAsset>>') => { elementType: 'shared<MaterialAsset>', length: undefined }\n * parseManagedArraySchema('array<shared<>>') => null (empty tag rejection)\n * parseManagedArraySchema('array<unique<X>>') => null (illegal element)\n * parseManagedArraySchema('array<array<f32,4>>') => null (nested rejected)\n *\n * Naming note (D-6 whitelist): `parseManagedArraySchema` keeps the\n * `Managed` infix because `managed = ECS-tracked` — every legal output\n * shape this parser returns is one whose lifecycle the ECS knows how to\n * retain / release on overwrite, despawn, or archetype migration.\n */\nexport function parseManagedArraySchema(\n fieldType: string,\n): { readonly elementType: ManagedArrayElementType; readonly length: number | undefined } | null {\n if (!fieldType.startsWith('array<') || !fieldType.endsWith('>')) return null;\n const inner = fieldType.slice(6, -1);\n const commaIdx = inner.indexOf(',');\n if (commaIdx === -1) {\n // Variable-capacity: inner must be a bare element-type keyword or\n // handle<X> template.\n if (!isValidArrayElementType(inner)) return null;\n return { elementType: inner as ManagedArrayElementType, length: undefined };\n }\n // Fixed-capacity: split at first comma; element-type before, integer length\n // after. Reject any further '<' / ':' / ',' to keep the form unambiguous.\n const head = inner.slice(0, commaIdx).trim();\n const tail = inner.slice(commaIdx + 1).trim();\n if (!isValidArrayElementType(head)) return null;\n if (!/^[1-9]\\d*$/.test(tail)) return null;\n return { elementType: head as ManagedArrayElementType, length: Number.parseInt(tail, 10) };\n}\n\n/**\n * Parse the byte count out of a `buffer<N>` schema keyword. Returns NaN if\n * the input does not match the keyword pattern - callers that already gated\n * via `isManagedBufferField` get a guaranteed-positive integer for the\n * fixed-byte form. The bare `'buffer'` keyword (variable byte capacity)\n * returns NaN and callers must check `fieldType === 'buffer'` separately.\n */\nexport function bufferFieldByteLength(fieldType: string): number {\n if (!fieldType.startsWith('buffer<') || !fieldType.endsWith('>')) return Number.NaN;\n const tail = fieldType.slice(7, -1);\n if (!/^[1-9]\\d*$/.test(tail)) return Number.NaN;\n return Number.parseInt(tail, 10);\n}\n\n/**\n * Runtime check for a schema-vocab keyword (the tier-2 surface).\n *\n * Pure-function regex match — kept off the hot path; only invoked by\n * `defineComponent` once per field at registration time. The match patterns\n * are the runtime mirror of `SchemaVocabKeyword` template literals.\n *\n * - `'string'` is exact-match (bare literal, no `<>`).\n * - `'buffer'` is exact-match (variable-byte capacity).\n * - `buffer<N>` requires `N` to be a positive base-10 integer (`/^[1-9]\\d*$/`).\n * Forms like `buffer<abc>` / `buffer<0>` / `buffer<>` are rejected.\n * - `unique<T>` / `shared<T>` require a non-empty target tag (`/^\\w+$/`).\n * - `entity` is exact-match.\n * - `array<T,N>` / `array<T>` accept only the whitelist element types\n * (`MANAGED_ARRAY_ELEMENT_TYPES`); illegal inner types fall through and\n * the caller surfaces `managed-array-element-type-not-allowed`.\n */\nexport function isSchemaVocabKeyword(s: string): s is SchemaVocabKeyword {\n if (s === 'string') return true;\n if (s === 'entity') return true;\n if (s === 'buffer') return true;\n if (s.startsWith('buffer<') && s.endsWith('>')) {\n const tail = s.slice(7, -1);\n return /^[1-9]\\d*$/.test(tail);\n }\n if (s.startsWith('unique<') && s.endsWith('>')) {\n return /^\\w+$/.test(s.slice(7, -1));\n }\n if (s.startsWith('shared<') && s.endsWith('>')) {\n return /^\\w+$/.test(s.slice(7, -1));\n }\n if (s.startsWith('array<') && s.endsWith('>')) {\n return parseManagedArraySchema(s) !== null;\n }\n return false;\n}\n\n/**\n * JS value-shape per managed-array element type. `entity` maps to `Entity`\n * (branded number), every scalar maps to `number` (bool is stored as a 0/1\n * byte and read back as 0 or 1).\n */\nexport type ManagedArrayElementValue<T extends ManagedArrayElementType> = T extends 'entity'\n ? EntityHandle\n : number;\n\n/**\n * Maps each field-type keyword to the JS value type read/written by it.\n *\n * Tier-1 (legacy scalars) widens to `boolean | number`; tier-2 (schema-vocab)\n * resolves to the corresponding handle / entity / buffer / array / string\n * shape via the `infer T` template-literal extraction pattern. Conditional\n * types resolve top-down --- the `'string'` arm sits BEFORE the array<...> /\n * `buffer<N>` arms so the precise literal wins template-literal resolution\n * (R-P5: prevents `'string'` from being shadowed by a wider template-literal\n * pattern). The fixed-capacity `array<T,N>` arm matches before the\n * variable-capacity `array<T>` arm by the same rule.\n *\n * The 4 buffer/array keywords (`'buffer'` / `'buffer<N>'` / `'array<T>'` /\n * `'array<T, N>'`) all resolve directly to a concrete TypedArray (or\n * Uint8Array for the byte-only buffer family). At the public `world.get`\n * boundary, a relationship-target `array<entity>` is a detached `Uint32Array`\n * snapshot. Other public array fields retain their existing transient live\n * TypedArray alias: fixed `buffer<N>` / `array<T,N>` values alias the inline\n * column buffer (feat-20260602), while variable `buffer` / `array<T>` values\n * alias the BufferPool slot bytes. Internal `readRow`, `_getArrayView`, and\n * `materializeArrayView` paths always use the live zero-copy alias. Mutation\n * flows through `world.set` / `world.push` / `world.pop`, not direct\n * assignment to a returned TypedArray.\n *\n * The `'string'` arm resolves to a native JS `string` (D-R1 / AC-13): the\n * dispatch routes the column u32 through `UniqueRefStore.resolve(handle)`\n * which returns the immutable string payload by reference.\n */\nexport type FieldValueType<T extends SchemaFieldType> = T extends 'bool'\n ? boolean\n : T extends 'entity'\n ? EntityHandle | null\n : T extends 'string'\n ? string\n : T extends 'buffer'\n ? Uint8Array\n : T extends `buffer<${number}>`\n ? Uint8Array\n : T extends `array<shared<${infer Target}>, ${number}>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<shared<${infer Target}>>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem>\n : T extends `array<${infer Elem extends ManagedArrayElementType}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem>\n : T extends `unique<${infer Target}>`\n ? Handle<Target, 'unique'>\n : T extends `shared<${infer Target}>`\n ? Handle<Target, 'shared'>\n : T extends ScalarFieldType\n ? number\n : never;\n\n/**\n * Input-side counterpart of {@link FieldValueType} for write paths\n * (`world.spawn` / `world.addComponent` / `world.set`).\n *\n * Asymmetric on `array<scalar, N>` / `array<scalar>` ONLY: the read side\n * surfaces zero-copy `Float32Array` / `Uint32Array` / etc views; the write\n * side ALSO accepts `readonly number[]` because writeArrayField copies bytes\n * verbatim from either shape (TypedArray subarray() OR per-element pack via\n * DataView). Plain literals like `times: [0.5]` reach the same code path\n * with no Float32Array wrapper boilerplate at the call site, and short\n * prefixes pad the row tail with zero (writeArrayField D-3 contract).\n *\n * Asymmetric on `buffer` / `buffer<N>`: the read side returns `Uint8Array`,\n * but the write side accepts any `AllowSharedBufferSource` (Float32Array /\n * ArrayBuffer / Uint8Array / any TypedArray). The ECS buffer-write ingestion\n * point (`World.writeRow` / `World.set`) normalizes any view to `Uint8Array`\n * over its raw bytes before storing (feat-20260621 V2 / AC-A4). This lets AI\n * users write typed param payloads directly, e.g.\n * `world.set(e, PostProcessParams, { data: Float32Array.of(exposure,0,0,0) })`,\n * without manual byte-reinterpret boilerplate at the call site.\n *\n * Every other arm matches FieldValueType verbatim (no widening): handles\n * are already arrays-of-handle, scalars stay number, etc.\n */\nexport type FieldInputType<T extends SchemaFieldType> = T extends 'bool'\n ? boolean\n : T extends 'entity'\n ? EntityHandle | null\n : T extends 'string'\n ? string\n : T extends 'buffer'\n ? AllowSharedBufferSource\n : T extends `buffer<${number}>`\n ? AllowSharedBufferSource\n : T extends `array<shared<${infer Target}>, ${number}>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<shared<${infer Target}>>`\n ? readonly Handle<Target, 'shared'>[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem> | readonly number[]\n : T extends `array<${infer Elem extends ManagedArrayElementType}>`\n ? TypedArrayFor<Elem extends 'entity' ? 'u32' : Elem> | readonly number[]\n : T extends `unique<${infer Target}>`\n ? Handle<Target, 'unique'>\n : T extends `shared<${infer Target}>`\n ? Handle<Target, 'shared'>\n : T extends ScalarFieldType\n ? number\n : never;\n\n/**\n * Maps a SchemaFieldType to its zero-copy query-column view type.\n *\n * Three storage shapes share the keyword space:\n *\n * 1. Scalar / fixed-inline columns -- the column buffer is the data, written\n * in place. The bundle entry is a concrete writable TypedArray of the\n * correct ctor (`f32` -> `Float32Array`, `'buffer<N>'` -> `Uint8Array`,\n * `'array<T,N>'` -> the T-typed array). Direct index assignment is\n * fine -- the column owns the bytes.\n *\n * 2. `shared\\<X\\>` (rc-tracked AssetRegistry reference) -- the column carries\n * a u32 handle id; SharedRefStore owns the rc lifecycle. The bundle\n * entry is a `ManagedColumnReader<T>` (D-4 / D-7) -- read-only, walk\n * via `.get(i)`. Consumers route through `assets.get(handle)` to\n * materialise the asset payload.\n *\n * 3. The 4 managed-vocab keywords -- `'string'` / `` `ref<T>` `` / variable\n * `'buffer'` / variable `` `array<T>` `` -- the column carries a u32 slot\n * id; the payload lives in `UniqueRefStore` / `BufferPool`. The bundle\n * entry is a `ManagedColumnReader<T>` (D-4 / D-7) -- read-only by\n * construction, no index signature. Mutation MUST flow through the\n * public dispatch (`world.set` / `world.push` / `world.allocUniqueRef`).\n *\n * The `extends SchemaFieldType` upper bound matches `ComponentSchema[K]`\n * so query bundle types do not have to pre-filter.\n */\nexport type TypedArrayFor<T extends SchemaFieldType> = T extends 'f32'\n ? Float32Array\n : T extends 'f64'\n ? Float64Array\n : T extends 'i32'\n ? Int32Array\n : T extends 'u32' | 'enum' | 'ref' | 'entity'\n ? Uint32Array\n : T extends 'i16'\n ? Int16Array\n : T extends 'u16'\n ? Uint16Array\n : T extends 'i8'\n ? Int8Array\n : T extends 'u8' | 'bool'\n ? Uint8Array\n : T extends 'string'\n ? ManagedColumnReader<'string'>\n : T extends `unique<${string}>`\n ? ManagedColumnReader<T>\n : T extends `shared<${string}>`\n ? ManagedColumnReader<T>\n : T extends 'buffer'\n ? ManagedColumnReader<'buffer'>\n : T extends `buffer<${number}>`\n ? Uint8Array\n : T extends `array<${infer Elem extends ManagedArrayElementType}, ${number}>`\n ? TypedArrayFor<\n Elem extends 'entity' | `shared<${string}>` ? 'u32' : Elem\n >\n : T extends `array<${string}>`\n ? ManagedColumnReader<T>\n : never;\n\n/**\n * Relationship metadata (feat-20260531 M2 / plan-strategy D-5). Declares this\n * component as the holder side of a Bevy-style bidirectional relationship: the\n * holder carries a single `entity` field (the target), and the engine mirrors\n * the reverse reference into `mirror`.`field` (an `array<entity>` on the target\n * entity) at add / remove / despawn time.\n *\n * - `mirror` — the mirror component's string NAME (not a type reference, so\n * `engine-ecs` never imports the mirror component type; AC-29). The mirror\n * component is a derived runtime view rebuilt by the relationship owner, so\n * it MUST declare `transient: true` — otherwise scene collect serializes it\n * and `instantiateScene` double-writes (serialized copy + owner rebuild).\n * - `field` — the `array<entity>` field on the mirror component that holds the\n * reverse list. Validated to be exactly `'array<entity>'` at `defineComponent` time.\n * - `exclusive` — when `true`, re-adding the holder component with a new target\n * auto-reparents (clears the old mirror entry, then appends the new one)\n * instead of returning `ComponentAlreadyPresentError` (AC-12).\n * - `linkedSpawn` — when `true`, despawning the target recursively despawns the\n * holders in its mirror list. Default `false` (D-1): despawn only prunes the\n * mirror entry, the holder entity survives.\n */\n/** A schema is a record of field-name → field-type keyword. */\nexport type ComponentSchema = Record<string, SchemaFieldType>;\n\n/** Derive the JS value-shape from a schema (read side; zero-copy views). */\nexport type ShapeOf<S extends ComponentSchema> = {\n [K in keyof S]: FieldValueType<S[K]>;\n};\n\n/**\n * Derive the input-side value-shape from a schema (write side; widens\n * `array<scalar>` to also accept `readonly number[]` plus the strict\n * TypedArray view). Used by `world.spawn` / `world.addComponent` /\n * `world.set` `data` so AI users can write `times: [0.5]` instead of the\n * `new Float32Array([0.5])` boilerplate. writeArrayField walks both shapes\n * via the same byte-copy path so runtime semantics are identical.\n */\nexport type InputShapeOf<S extends ComponentSchema> = {\n [K in keyof S]: FieldInputType<S[K]>;\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// ComponentId\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Numeric identity is an ECS-owner fact, not component authoring data. Keep it\n * out of the token's own enumerable surface so reflection sees only\n * `name`/`fields`/`storage`.\n */\n/** Component owner identity shared by independently bundled ECS entry points. */\nconst COMPONENT_OWNER_REGISTRY = Symbol.for('forgeax.ecs.componentOwnerRegistry');\ninterface ComponentOwnerRegistry {\n nextId: number;\n readonly ids: WeakMap<object, ComponentId>;\n readonly schemas: WeakMap<object, Readonly<Record<string, SchemaFieldType>>>;\n}\nconst ownerSymbols = globalThis as typeof globalThis & { [key: symbol]: unknown };\nconst ownerRegistry =\n (ownerSymbols[COMPONENT_OWNER_REGISTRY] as ComponentOwnerRegistry | undefined) ??\n (() => {\n const registry: ComponentOwnerRegistry = {\n nextId: 1,\n ids: new WeakMap<object, ComponentId>(),\n schemas: new WeakMap<object, Readonly<Record<string, SchemaFieldType>>>(),\n };\n ownerSymbols[COMPONENT_OWNER_REGISTRY] = registry;\n return registry;\n })();\n\nlet entityDefinitionSeen = false;\nlet componentDefinedBeforeEntity = false;\n\n/** @internal Barrel-only check for the id=0 Entity import-order invariant. */\nexport function isComponentDefinitionOrderValid(): boolean {\n return !componentDefinedBeforeEntity;\n}\n\n/** @internal Read the owner-assigned identity for storage/archetype code. */\nexport function componentId(component: Component): ComponentId {\n const id = ownerRegistry.ids.get(component);\n if (id === undefined) throw new Error(`Component identity missing for '${component.name}'.`);\n return id;\n}\n\n/** @internal Derive the flat type map from the fields SSOT. */\nexport function componentSchema<const C extends Component>(component: C): Readonly<SchemaOf<C>> {\n const schema = ownerRegistry.schemas.get(component);\n if (schema === undefined) throw new Error(`Component schema missing for '${component.name}'.`);\n return schema as Readonly<SchemaOf<C>>;\n}\n\n/** Numeric identifier for a component type, used by bitmask matching and archetype edges. */\nexport type ComponentId = number;\nexport type ComponentStorage = 'table' | 'sparse';\n\n// ────────────────────────────────────────────────────────────────────────────\n// Token\n// ────────────────────────────────────────────────────────────────────────────\n\ndeclare const __componentBrand: unique symbol;\n\n/**\n * Opaque component token. Carries the component name `N` as a string-literal\n * type parameter (lifted from the `defineComponent` call site via `<const N>`)\n * and the schema-shape `S` as a phantom brand so `world.get(e, Comp)` can\n * return `Result<ShapeOf<S>, EcsError>` precisely.\n *\n * The `N` parameter defaults to `string` to keep existing single-parameter\n * `Component<S>` annotations source-compatible. When inferred from a\n * `defineComponent('Position', ...)` call, `N` is the literal `'Position'`,\n * which lets query row/span mapped types resolve `{ [K in N]: ... }` to a\n * concrete keyed object instead of a degraded index signature (KD-1).\n */\nexport interface Component<N extends string = string, S extends ComponentSchema = ComponentSchema> {\n readonly name: N;\n /** The one schema projection: type, default, and enum labels per field. */\n readonly fields: Readonly<Record<keyof S & string, FieldReflection>>;\n readonly storage: ComponentStorage;\n readonly [__componentBrand]: ShapeOf<S>;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// TypedArray constructors — internal; consumed by `scalarRow()` to build\n// TYPE_METADATA rows (feat-20260602 M4, w12).\n// ────────────────────────────────────────────────────────────────────────────\n\n/** TypedArray constructor for each scalar field type. */\nconst VIEW_CTORS: Readonly<\n Record<\n ScalarFieldType,\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n >\n> = {\n f32: Float32Array,\n f64: Float64Array,\n i32: Int32Array,\n u32: Uint32Array,\n i16: Int16Array,\n u16: Uint16Array,\n i8: Int8Array,\n u8: Uint8Array,\n bool: Uint8Array,\n enum: Uint32Array,\n ref: Uint32Array,\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// TYPE_METADATA — global per-type metadata table (feat-20260602 M1 / D-A6)\n//\n// Converges the 12 scattered type-intrinsic structures (3 tables + 9\n// predicate / tool functions) into a single per-type authoritative table.\n// Exports FIELD_SIZE_BYTES / VIEW_CTORS / isSchemaVocabKeyword /\n// managedArrayElementBytes / SUPPORTED_FIELD_TYPES / storageFieldType were\n// deleted M4 (w12); internal FIELD_SIZE_BYTES + VIEW_CTORS constants remain as\n// build inputs for scalarRow(). All former consumers now read TYPE_METADATA:\n// storage routing via fieldTypeToMetaKey() + TYPE_METADATA[key].storage,\n// scalar checks via TYPE_METADATA[key]?.isScalar.\n//\n// Mixed key granularity (D-5): the 11 scalars are keyed by their concrete\n// type (`f32` ... `ref`); the 6 vocab families are keyed by family (`entity`\n// / `string` / `buffer` / `ref` / `handle` / `array`). The `array` row's T/N\n// parameters are NOT table columns — they are parsed per-field into\n// `arrayMeta` (see FieldDescriptor below). The vocab `ref` family row and\n// the scalar `ref` row share the `'ref'` key intentionally: the scalar is a\n// u32 column placeholder and the vocab `ref<T>` form maps to the same\n// managed-ref storage, so one row carries both (isScalar + isManaged both\n// true). tweak-20260612-ecs-concept-compression dropped redundant columns:\n// `isVocabKeyword` (zero production consumers), the per-vocab managed-\n// ref predicate column (100% duplicate of `isManaged`), and the YAGNI\n// `fixedByteLength` placeholder;\n// `isLegacyScalar` was renamed `isScalar` (the \"legacy\" prefix labelled the\n// historical M2-introduction tense; the 11 scalars are first-class).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * One row of the global type-metadata table. Carries the type-intrinsic\n * properties a field type has regardless of which component declares it.\n *\n * - `byteSize` — element byte width for the column-storage scalar; `undefined`\n * for families whose storage byte size is not a fixed per-type constant\n * (variable buffer / array slot ids are u32-stored, surfaced via `storage`).\n * - `viewCtor` — TypedArray constructor for the column storage; `undefined`\n * for families without a direct TypedArray column.\n * - `storage` — the column-storage scalar type this field routes to (every\n * vocab family stores a u32 slot id / handle).\n * - `isScalar` — member of the 11 concrete scalar types\n * (`f32`/`f64`/`i32`/`u32`/`i16`/`u16`/`i8`/`u8`/`bool`/`enum`/`ref`).\n * - `isManaged` — routed through `UniqueRefStore` (string / ref<T>).\n * - `isBuffer` — a `buffer` / `buffer<N>` managed-byte slot.\n * - `isEntityRef` — the single-entity `entity` reference keyword.\n * - `isArray` — an `array<T>` / `array<T,N>` keyword.\n */\nexport interface TypeMetadataRow {\n readonly byteSize: number | undefined;\n readonly viewCtor:\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | undefined;\n readonly storage: ScalarFieldType;\n readonly isScalar: boolean;\n readonly isManaged: boolean;\n readonly isBuffer: boolean;\n readonly isEntityRef: boolean;\n readonly isArray: boolean;\n}\n\n/** Build a scalar row from the concrete scalar type. */\nfunction scalarRow(t: ScalarFieldType): TypeMetadataRow {\n return {\n byteSize: FIELD_SIZE_BYTES[t],\n viewCtor: VIEW_CTORS[t],\n storage: t,\n isScalar: true,\n // The scalar `ref` shares its key with the vocab `ref<T>` family; mark\n // it as managed so the single row covers both.\n isManaged: t === 'ref',\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n };\n}\n\n/**\n * Global per-type metadata table. Keyed by concrete scalar type (11) plus\n * vocab family (6 — `entity` / `string` / `buffer` / `ref` / `handle` /\n * `array`). The `ref` key is shared by the legacy scalar and the vocab family\n * (see header). Every vocab family stores a u32 slot id / handle.\n *\n * Built once at module load; frozen so downstream consumers (column.ts /\n * archetype.ts / world.ts, migrated M2) read a stable single source.\n */\nexport const TYPE_METADATA: Readonly<Record<string, TypeMetadataRow>> = Object.freeze({\n f32: scalarRow('f32'),\n f64: scalarRow('f64'),\n i32: scalarRow('i32'),\n u32: scalarRow('u32'),\n i16: scalarRow('i16'),\n u16: scalarRow('u16'),\n i8: scalarRow('i8'),\n u8: scalarRow('u8'),\n bool: scalarRow('bool'),\n enum: scalarRow('enum'),\n ref: scalarRow('ref'),\n entity: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: false,\n isEntityRef: true,\n isArray: false,\n },\n string: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: true,\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n },\n buffer: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: true,\n isEntityRef: false,\n isArray: false,\n },\n // feat-20260614-ecs-shared-component-and-unique-rename M3 (plan-strategy\n // D-3): independent `'shared'` row, NOT a reuse of the `'ref'` (post-M2:\n // `'unique<T>'` family) row. `isManaged: true` so write-barrier dispatch\n // routes shared<T> fields through release on despawn / removeComponent /\n // set-overwrite, but the M4 sub-dispatch in releaseManagedFieldOnRow will\n // separate shared (rc--) from unique (direct slot drop) using the\n // fieldType.startsWith('shared<') predicate. Keeping the meta key\n // independent preserves the \"meta key = release semantics\" invariant\n // (architecture-principles.md #1 SSOT).\n shared: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: true,\n isBuffer: false,\n isEntityRef: false,\n isArray: false,\n },\n array: {\n byteSize: 4,\n viewCtor: Uint32Array,\n storage: 'u32',\n isScalar: false,\n isManaged: false,\n isBuffer: false,\n isEntityRef: false,\n isArray: true,\n },\n});\n\n// ────────────────────────────────────────────────────────────────────────────\n// FieldDescriptor — input field-descriptor object + per-field reflection\n// (feat-20260602 M1 / D-A1 / D-A3)\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Pre-parsed `array<T>` / `array<T,N>` reflection. Bare length sentinel\n * (D-A1 user ruling): `length` present => fixed-capacity, `length === undefined`\n * => variable-capacity. No `isVariable` / `kind` field — both are losslessly\n * derivable from `length` presence (architecture-principles.md #2 Derive). This\n * is exactly the existing `parseManagedArraySchema` return shape (zero shape\n * change).\n */\nexport interface ArrayMeta {\n readonly elementType: ManagedArrayElementType;\n readonly length?: number;\n}\n\n/**\n * Input field-descriptor object (D-A3). The second `defineComponent` argument\n * may declare each field either as a bare type keyword (legacy flat form,\n * still accepted through M2; migrated repo-wide in M3) or as a descriptor\n * object aggregating `type` + `default` + semantic `shape` + field-level\n * `meta`.\n *\n * - `type` — the schema field-type keyword (a parametrized string such as\n * `'array<f32,3>'` / `'unique<MaterialAsset>'` is used verbatim, D-A2).\n * - `default` — layer-2 default value; retained in the field reflection row.\n * - `shape` — producer-owned semantic shape tag for schema consumers; it does\n * not change ECS storage or runtime value semantics.\n * - `meta` — field-level open namespace; aggregated into `component.meta`. The\n * infra gives no key special meaning (open map, OOS-1).\n * - `transient` — when `true`, scene collect skips this field (D-5). Same word,\n * same meaning as the component-level `transient` flag, with granularity sunk\n * to the field level: a field that is derived/reconstructable (e.g. a resolved\n * world mat4) is excluded from serialization while its component's persisted\n * fields still round-trip. Absent (the common case) means the field is\n * serialized.\n * - `labels` — for an `enum` field ONLY: the label→numeric-value map (e.g.\n * `{ static: 0, dynamic: 1, kinematic: 2 }`). An `enum` field stores a bare\n * `u32` variant index; the human-readable names historically lived in a\n * SEPARATE per-package const map (`RigidBodyTypeValue`) + comment table, so no\n * schema consumer could read them and the two could drift. Declaring `labels`\n * attaches that map to the field itself (SSOT-adjacent): it is aggregated into\n * `component.fields[field].labels` and surfaced by reflection consumers (the\n * editor's `describeComponent`, inspector UIs, validation hints) so a\n * docs-only user learns the legal variants + their integers from the schema\n * alone. Pass the EXISTING `*Value` const map here (Derive, don't Duplicate —\n * one object, two consumers). Absent for non-enum fields / enums that opt out.\n */\nexport interface FieldDescriptor<T extends SchemaFieldType = SchemaFieldType> {\n readonly type: T;\n readonly default?: FieldValueType<T>;\n /** Semantic authoring shape; storage still follows `type`. */\n readonly shape?: FieldShapeKind;\n readonly meta?: Readonly<Record<string, unknown>>;\n readonly transient?: boolean;\n readonly labels?: Readonly<Record<string, number>>;\n}\n\n/**\n * Per-field reflection produced at registration time and read off\n * `component.fields[fieldName]` (D-A3). Carries the pre-parsed facts: the\n * field `type`, its `default` (if any), semantic `shape` (if declared), — for\n * `array<...>` fields only — the pre-parsed `arrayMeta` (parse happens once at\n * registration, AC-03c), and the field-level `transient` flag (D-5) when\n * declared.\n *\n * `transient` mirrors the component-level `Component.transient` (same word,\n * same meaning): scene collect skips a `transient` field just as it skips a\n * `transient` component. Granularity is sunk to the field level so a component\n * can persist most of its fields while excluding a derived/reconstructable one\n * (e.g. `GlobalTransform.world`). Absent means the field participates in\n * serialization.\n */\nexport interface FieldReflection {\n readonly type: SchemaFieldType;\n readonly default?: unknown;\n /** Producer-declared semantic shape, when storage type alone is insufficient. */\n readonly shape?: FieldShapeKind;\n readonly arrayMeta?: ArrayMeta;\n readonly transient?: boolean;\n /**\n * For an `enum` field: the label→numeric-value map declared on the field\n * descriptor (see `FieldDescriptor.labels`). Lets a schema consumer resolve a\n * variant name ↔ its stored `u32` index without a separate const map. Absent\n * for non-enum fields / enums that did not declare labels.\n */\n readonly labels?: Readonly<Record<string, number>>;\n}\n\n/**\n * One input field-spec value: either the bare type keyword (legacy flat form)\n * or a field-descriptor object. Accepting both keeps the ~44 flat-string\n * call-sites + ~55 test files green through M1/M2 while the field-descriptor\n * form is migrated in repo-wide in M3 (D-A7 / D-A8 shrink the migration\n * surface to the input side only).\n */\nexport type FieldSpec<T extends SchemaFieldType = SchemaFieldType> = T | FieldDescriptor<T>;\n\n/** An input field-spec map: field-name -> bare keyword | field-descriptor. */\nexport type FieldsInput = Record<string, FieldSpec>;\n\n/**\n * Project an input field-spec map down to its flat `ComponentSchema` shape\n * (field-name -> type keyword). A bare-keyword spec maps to itself (identity,\n * so existing flat-string call-sites infer exactly as before); a descriptor\n * spec maps to its `type`. This keeps `Component<N, SchemaOf<F>>` driving every\n * downstream type (ShapeOf / query row/span projection / TypedArrayFor) unchanged.\n */\nexport type SchemaOf<F extends FieldsInput | Component> =\n F extends Component<string, infer S>\n ? S\n : F extends FieldsInput\n ? {\n [K in keyof F]: F[K] extends FieldDescriptor<infer T>\n ? T\n : F[K] extends SchemaFieldType\n ? F[K]\n : never;\n }\n : never;\n\n// ────────────────────────────────────────────────────────────────────────────\n// defineComponent\n// ────────────────────────────────────────────────────────────────────────────\n\n/** Optional configuration for `defineComponent` (w4, M3 consumer; w21 layer-2 defaults). */\nexport interface DefineComponentOptions {\n readonly storage?: ComponentStorage;\n /**\n * When `true`, the component is skipped by scene collect\n * (rootsToSceneAsset). The component stays in archetype columns and\n * participates normally in queries / world.get at runtime.\n *\n * Default: `false`. Mirror targets of relationship components should\n * declare `transient: true` so they are not serialized (their state is\n * rebuilt by the mirror hook after instantiateScene).\n */\n readonly transient?: boolean;\n /**\n * Components materialized automatically when this component is added.\n * Explicit data for a required component wins; the ECS appends only missing\n * identities at the spawn/add boundary, never from a frame system.\n */\n readonly requires?: readonly Component[];\n /**\n * Component-level open metadata namespace. Entries are copied into\n * `Component.meta` at registration; the ECS core assigns no meaning to any\n * key. Component-level entries win over field-level entries with the same\n * key, and consumers may extend the mutable map after registration.\n */\n readonly meta?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * Extract the bare field-type keyword from a field-spec (bare keyword | field-\n * descriptor object), fail-fast if a descriptor object is missing its `type`.\n * The throw carries the field name + expected shape (charter P3 / OOS-6: this\n * is a programmer error caught at registration time, no new EcsErrorCode).\n */\nfunction fieldSpecType(fieldName: string, spec: FieldSpec): SchemaFieldType {\n if (typeof spec === 'string') return spec as SchemaFieldType;\n const t = (spec as FieldDescriptor).type;\n if (typeof t !== 'string') {\n throw new SchemaUnsupportedFieldError(\n fieldName,\n `<field-descriptor missing 'type'> (expected { type, default?, meta? })`,\n );\n }\n return t as SchemaFieldType;\n}\n\n/**\n * Define a component. Returns a frozen opaque token with exactly three runtime\n * facts: `.name`, `.fields`, and `.storage`. Numeric identity, flat schema,\n * and defaults are owner projections held outside the token.\n *\n * The second argument accepts each field either as a bare type keyword\n * ('f32', 'array<entity>', ...) or as a field-descriptor object\n * `{ type, default?, meta? }` (D-A3). The single `FieldsInput` overload\n * handles both forms — bare keywords are identity through `SchemaOf<F>`.\n *\n * The `<const N>` modifier lifts `name` to its string-literal type so the\n * returned `Component<N, SchemaOf<S>>` drives precise key-based mapped types\n * downstream (for example QueryRow and QuerySpan projections). At runtime `name` is a plain\n * string.\n *\n * Schema-field validation accepts both the legacy scalar tier\n * (`ScalarFieldType`, 11 keywords) and the schema-vocab tier\n * (`SchemaVocabKeyword`, 8 patterns including `array<T,N>` / `array<T>` /\n * `buffer` / `buffer<N>`). Mismatched values raise\n * `SchemaUnsupportedFieldError`. Illegal `array<...>` element types\n * (e.g. `array<ref<X>>`) raise `ManagedArrayElementTypeNotAllowedError`\n * (AC-03 runtime fail-safe).\n *\n * Relationship roles are declared through `defineRelationship`; component\n * definitions contain only schema vocabulary and no mirror metadata.\n *\n * @throws SchemaUnsupportedFieldError for any field type not in the supported\n * set, or a field-descriptor object missing its `type`.\n * @throws ManagedArrayElementTypeNotAllowedError when an `array<...>`\n * keyword carries an illegal element type.\n * @throws RelationshipMirrorComponentNotRegisteredError when\n * `relationship.mirror` names a component not yet defined.\n * @throws RelationshipMirrorFieldTypeMismatchError when the mirror's\n * `relationship.field` is missing or not typed `'array<entity>'`.\n */\n// Single signature post-M4 (w12) — bare-keyword field specs are valid\n// FieldSpec<T> values (identity through SchemaOf<F>), so flat-string schemas\n// work without a separate overload. tweak-20260612-ecs-concept-compression\n// dropped the redundant byte-identical overload declaration.\nexport function defineComponent<const N extends string, const S extends FieldsInput>(\n name: N,\n fields: S,\n options?: DefineComponentOptions,\n): Component<N, SchemaOf<S>> {\n const storage = options?.storage ?? 'table';\n assertComponentStorage(storage);\n if (storage === 'sparse' && Object.keys(fields).length !== 0) {\n throw new SparseStorageRequiresTagError(name);\n }\n const schema: Record<string, SchemaFieldType> = {};\n const reflectedFields: Record<string, FieldReflection> = {};\n const collectedMeta: Record<string, unknown> = {};\n const collectedDefaults: Record<string, unknown> = {};\n\n for (const fieldName of Object.keys(fields)) {\n const spec = fields[fieldName] as FieldSpec;\n const fieldType = fieldSpecType(fieldName, spec);\n\n // Validate the field type — same fail-fast as before, now over the\n // normalized keyword.\n let arrayMeta: ArrayMeta | undefined;\n if (TYPE_METADATA[fieldType]?.isScalar === true) {\n // legacy scalar — ok\n } else if (fieldType === 'string') {\n // string vocab — ok\n } else if (fieldType.startsWith('array<') && fieldType.endsWith('>')) {\n const parsed = parseManagedArraySchema(fieldType);\n if (parsed === null) {\n const elementType = fieldType.slice(6, -1);\n throw new ManagedArrayElementTypeNotAllowedError(fieldName, elementType);\n }\n // Pre-parse once at registration (AC-03c): array fields cache arrayMeta.\n // Bare length sentinel {elementType, length?} (D-A1): drop `length` when\n // variable so the row is byte-identical to the parse return shape.\n arrayMeta = deepFreeze(\n parsed.length === undefined\n ? { elementType: parsed.elementType }\n : { elementType: parsed.elementType, length: parsed.length },\n );\n } else if (!isSchemaVocabKeyword(fieldType)) {\n throw new SchemaUnsupportedFieldError(fieldName, fieldType);\n }\n\n schema[fieldName] = fieldType;\n\n // Per-field reflection row — only attach arrayMeta / default when present\n // (exactOptionalPropertyTypes: never set an explicit `undefined`).\n const row: {\n type: string;\n default?: unknown;\n shape?: FieldShapeKind;\n arrayMeta?: ArrayMeta;\n transient?: boolean;\n labels?: Readonly<Record<string, number>>;\n } = {\n type: fieldType,\n };\n if (typeof spec !== 'string') {\n const desc = spec as FieldDescriptor;\n if ('default' in desc) {\n row.default = desc.default;\n collectedDefaults[fieldName] = desc.default;\n }\n if (desc.shape !== undefined) row.shape = desc.shape;\n if (desc.meta !== undefined) {\n Object.assign(collectedMeta, desc.meta);\n }\n // exactOptionalPropertyTypes: only attach `transient` when declared.\n if (desc.transient !== undefined) row.transient = desc.transient;\n // enum label→value map (see FieldDescriptor.labels). Frozen so the\n // reflected row exposes a stable read-only map. Only enum fields declare it.\n if (desc.labels !== undefined) row.labels = deepFreeze({ ...desc.labels });\n }\n if (arrayMeta !== undefined) row.arrayMeta = arrayMeta;\n reflectedFields[fieldName] = Object.freeze(row) as FieldReflection;\n }\n\n if (name === 'Entity') {\n entityDefinitionSeen = true;\n } else if (!entityDefinitionSeen) {\n componentDefinedBeforeEntity = true;\n }\n const id = name === 'Entity' ? 0 : ownerRegistry.nextId++;\n\n // Derived defaults projection (D-A8): pure from `fields[k].default`.\n // No longer merged with a removed `options.defaults` input — strict single-entry\n // means the only way to set a layer-2 default is through the field descriptor.\n const frozenDefaults =\n Object.keys(collectedDefaults).length === 0\n ? undefined\n : (deepFreeze(collectedDefaults) as Readonly<Partial<ShapeOf<SchemaOf<S>>>>);\n\n // Keep the component token immutable while leaving its open metadata map\n // extensible for higher-level consumers after registration.\n if (options?.meta !== undefined) {\n Object.assign(collectedMeta, options.meta);\n }\n const frozenSchema = deepFreeze(schema);\n const frozenFields = deepFreeze(reflectedFields);\n const meta = collectedMeta;\n const token = Object.freeze({ name, fields: frozenFields, storage }) as Component<N, SchemaOf<S>>;\n ownerRegistry.ids.set(token, id);\n ownerRegistry.schemas.set(token, frozenSchema);\n registerComponentDefinition(token, {\n fields: frozenFields,\n defaults: frozenDefaults,\n policy: {\n transient: options?.transient ?? false,\n meta,\n requires: Object.freeze([...(options?.requires ?? [])]),\n },\n });\n return Object.freeze(token);\n}\n\nexport class ComponentInUseError extends Error {\n override readonly name = 'ComponentInUseError';\n readonly code = 'component-in-use' as const;\n readonly expected = 'the component to have no live entity or scheduled-system references';\n readonly hint =\n 'Remove owning systems and component values before disposing the registration lease.';\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n super(`Component ${componentName} is still in use.`);\n this.detail = { componentName };\n }\n}\n\nexport class ComponentNameConflictError extends Error {\n override readonly name = 'ComponentNameConflictError';\n readonly code = 'component-name-conflict' as const;\n readonly expected = 'one component token per name in a World';\n readonly hint =\n 'Use the token already registered in this World or choose a distinct component name.';\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n super(`Component ${componentName} is already registered with a different token.`);\n this.detail = { componentName };\n }\n}\n\nexport type ComponentCatalogError = ComponentInUseError | ComponentNameConflictError;\n\nexport interface ComponentLease {\n readonly component: Component;\n dispose(): Result<void, ComponentInUseError>;\n}\n\ninterface ComponentRegistration {\n readonly component: Component;\n owners: number;\n}\n\n/** World-local discovery and ownership boundary for plugin-installed component vocabulary. */\nexport class ComponentCatalog {\n private readonly registrations = new Map<string, ComponentRegistration>();\n\n constructor(private readonly inUse: (component: Component) => boolean) {}\n\n register(component: Component): Result<ComponentLease, ComponentNameConflictError> {\n const current = this.registrations.get(component.name);\n if (current !== undefined && current.component !== component) {\n return err(new ComponentNameConflictError(component.name));\n }\n if (current === undefined) {\n this.registrations.set(component.name, { component, owners: 1 });\n } else {\n current.owners += 1;\n }\n\n let active = true;\n return ok({\n component,\n dispose: () => {\n if (!active) return ok(undefined);\n const registration = this.registrations.get(component.name);\n if (registration === undefined || registration.component !== component) {\n active = false;\n return ok(undefined);\n }\n if (registration.owners > 1) {\n registration.owners -= 1;\n active = false;\n return ok(undefined);\n }\n if (this.inUse(component)) return err(new ComponentInUseError(component.name));\n this.registrations.delete(component.name);\n active = false;\n return ok(undefined);\n },\n });\n }\n\n resolve(name: string): Component | undefined {\n return this.registrations.get(name)?.component;\n }\n\n entries(): ReadonlyMap<string, Component> {\n return new Map(\n [...this.registrations].map(([name, registration]) => [name, registration.component]),\n );\n }\n}\n","// @forgeax/engine-ecs/internal — package-owner seams that are intentionally\n// absent from the public ECS barrel.\n//\n// This entry keeps the existing component-owner imports stable and adds the\n// typed symbol used by scene propagation. A symbol avoids putting a derived\n// writer method on the discoverable Query contract while still letting the\n// owning package share the exact query implementation without a private path\n// import or an untyped cast at each consumer.\n\n// Keep this entry limited to package-owner metadata and the derived writer.\n// Raw World storage stays source-private; semantic World reads live behind the\n// separate `./world-read` entry.\nexport { componentId, componentSchema } from './component';\nexport { componentDefinition } from './component-schema';\n\nimport type { Result } from '@forgeax/engine-types';\nimport type { Component } from './component';\nimport type { QuerySpanUnavailableError } from './errors';\nimport type { DerivedRangeWriter } from './query/derived-range-writer';\nimport type { Query } from './query/query';\n\n/** Internal identity for the ECS-owned derived range writer accessor. */\nexport const DERIVED_WRITER: unique symbol = Symbol.for(\n 'forgeax.ecs.query.derivedWriter',\n) as unknown as typeof DERIVED_WRITER;\n\ntype DerivedWriterQuery<\n R extends readonly Component[],\n W extends readonly Component[],\n O extends readonly Component[],\n> = Query<R, W, O> & {\n readonly [DERIVED_WRITER]: <C extends W[number]>(\n component: C,\n ) => Result<DerivedRangeWriter<R[number], C>, QuerySpanUnavailableError>;\n};\n\n/**\n * Resolve the ECS-owned derived writer through its internal symbol seam.\n * Consumers of this module retain the component/query type relationship while\n * the public Query and QuerySpan surfaces remain read/write-only contracts.\n */\nexport function getDerivedWriter<\n R extends readonly Component[],\n W extends readonly Component[],\n O extends readonly Component[],\n C extends W[number],\n>(\n query: Query<R, W, O>,\n component: C,\n): Result<DerivedRangeWriter<R[number], C>, QuerySpanUnavailableError> {\n const internalQuery = query as DerivedWriterQuery<R, W, O>;\n return internalQuery[DERIVED_WRITER](component);\n}\n\nexport type {\n DerivedColumnBinding,\n DerivedRangeCursor,\n DerivedRangeRowCommit,\n DerivedRangeRowProbe,\n DerivedRangeWriter,\n} from './query/derived-range-writer';\n"]}