@forgeax/engine-ecs 0.1.33 → 0.1.34

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 (62) hide show
  1. package/README.md +35 -18
  2. package/dist/__tests__/query-idle.unit.test.d.ts +2 -0
  3. package/dist/__tests__/query-idle.unit.test.d.ts.map +1 -0
  4. package/dist/__tests__/set-allocation.unit.test.d.ts +2 -0
  5. package/dist/__tests__/set-allocation.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/state-projection.unit.test.d.ts +2 -0
  7. package/dist/__tests__/state-projection.unit.test.d.ts.map +1 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.mjs +579 -623
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/projection/index.d.ts +2 -18
  13. package/dist/projection/index.d.ts.map +1 -1
  14. package/dist/projection/index.mjs +433 -23
  15. package/dist/projection/index.mjs.map +1 -1
  16. package/dist/projection/state-projection.d.ts +37 -0
  17. package/dist/projection/state-projection.d.ts.map +1 -0
  18. package/dist/query/query.d.ts.map +1 -1
  19. package/dist/shared-ref-store.d.ts +0 -24
  20. package/dist/shared-ref-store.d.ts.map +1 -1
  21. package/dist/shared.mjs.map +1 -1
  22. package/dist/storage/archetype-graph.d.ts +2 -0
  23. package/dist/storage/archetype-graph.d.ts.map +1 -1
  24. package/dist/storage/change-detection.d.ts +4 -0
  25. package/dist/storage/change-detection.d.ts.map +1 -1
  26. package/dist/storage/table.d.ts +6 -3
  27. package/dist/storage/table.d.ts.map +1 -1
  28. package/dist/world-internal.d.ts +0 -2
  29. package/dist/world-internal.d.ts.map +1 -1
  30. package/dist/world.d.ts +0 -2
  31. package/dist/world.d.ts.map +1 -1
  32. package/package.json +4 -4
  33. package/src/__tests__/component-version-surface.test.ts +1 -7
  34. package/src/__tests__/derived-range-writer.contract.test.ts +6 -0
  35. package/src/__tests__/ecs-core-reduction.characterization.test.ts +2 -3
  36. package/src/__tests__/execution-conflict-boundary.unit.test.ts +4 -0
  37. package/src/__tests__/externalization-render-read-lease.unit.test.ts +4 -10
  38. package/src/__tests__/query-idle.unit.test.ts +19 -0
  39. package/src/__tests__/set-allocation.unit.test.ts +30 -0
  40. package/src/__tests__/shared-ref-lifetime.unit.test.ts +1 -2
  41. package/src/__tests__/shared-ref-store.unit.test.ts +2 -34
  42. package/src/__tests__/state-projection.unit.test.ts +159 -0
  43. package/src/__tests__/world-health.contract.test.ts +0 -23
  44. package/src/index.ts +1 -1
  45. package/src/projection/index.ts +9 -39
  46. package/src/projection/state-projection.ts +250 -0
  47. package/src/query/query.ts +23 -16
  48. package/src/shared-ref-store.ts +0 -70
  49. package/src/storage/archetype-graph.ts +15 -1
  50. package/src/storage/change-detection.ts +36 -4
  51. package/src/storage/table.ts +20 -1
  52. package/src/world-internal.ts +0 -2
  53. package/src/world.ts +35 -38
  54. package/dist/__tests__/structural-evidence.contract.test-d.d.ts +0 -2
  55. package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +0 -1
  56. package/dist/__tests__/structural-evidence.contract.test.d.ts +0 -2
  57. package/dist/__tests__/structural-evidence.contract.test.d.ts.map +0 -1
  58. package/dist/storage/structural-evidence.d.ts +0 -30
  59. package/dist/storage/structural-evidence.d.ts.map +0 -1
  60. package/src/__tests__/structural-evidence.contract.test-d.ts +0 -6
  61. package/src/__tests__/structural-evidence.contract.test.ts +0 -49
  62. package/src/storage/structural-evidence.ts +0 -64
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/component-schema.ts","../../src/errors/query-and-component-errors.ts","../../src/errors/sprite-and-shared-errors.ts","../../src/errors/validation-errors.ts","../../src/world-internal.ts","../../src/errors.ts","../../src/component.ts","../../src/entity-handle.ts","../../src/component-default-fallback.ts","../../src/projection/index.ts"],"names":["componentId"],"mappings":"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","file":"index.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 * Returned via `Result.err` from `world.removeComponent` when the caller tries\n * to remove an essential (undeletable) component\n * (feat-20260602-archetype-stores-full-packed-entity M1 / w3, plan-strategy\n * D-3). The only essential component today is the id=0 `Entity` component: every\n * archetype carries it unconditionally as the row's own packed handle, so\n * removing it is structurally meaningless. The code name is deliberately\n * generic (`remove-essential-component`, not entity-specific) so a future second\n * essential component reuses it without a rename.\n *\n * `.code = 'remove-essential-component'`\n * `.detail = { componentName }`\n * `.hint` — names the essential component + states it cannot be removed.\n */\nexport class RemoveEssentialComponentError extends Error {\n override readonly name = 'RemoveEssentialComponentError';\n readonly code = 'remove-essential-component' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n const hint = `Component \"${componentName}\" is essential (every entity carries it unconditionally) and cannot be removed. Despawn the entity instead if you want to retire it.`;\n const expected = 'non-essential component';\n super(\n `removeComponent: essential component cannot be removed.\\n` +\n ` code: remove-essential-component\\n` +\n ` component: ${componentName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { componentName };\n }\n}\n\n/**\n * Returned via the `Result` err branch when `instantiate` encounters a\n * SceneAsset entity whose `components` map references a component name that was\n * never passed to `defineComponent`.\n *\n * `.code = 'component-not-defined'`\n * `.detail.name` — the offending component name.\n *\n * Promoting this to a class (rather than a bare object literal) keeps the\n * scene-instantiate failure surface inside the `EcsError` class union, so the\n * documented two-level narrow `cause instanceof EcsError` actually matches it\n * (docs/feedbacks/2026-06-03 §6.2 Tier 4.2). `expected` / `hint` accept\n * per-call overrides because the parent-passthrough (ChildOf) site needs a\n * distinct message from the generic entity-component site.\n */\nexport class ComponentNotDefinedError extends Error {\n override readonly name = 'ComponentNotDefinedError';\n readonly code = 'component-not-defined' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly name: string };\n\n constructor(componentName: string, opts?: { expected?: string; hint?: string }) {\n const expected = opts?.expected ?? `component '${componentName}' defined before instantiate`;\n const hint =\n opts?.hint ??\n `define the component via defineComponent('${componentName}', ...) before instantiating this SceneAsset`;\n super(\n `instantiate: component not defined.\\n` +\n ` code: component-not-defined\\n` +\n ` component: ${componentName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { name: componentName };\n }\n}\n\n/**\n * Returned via `Result.err` from `world.spawn` / `world.addComponent` /\n * `world.instantiateScene` / `Commands.spawn` when the caller-supplied\n * data payload carries a key that is not declared in the target component's\n * schema. The pre-fix behaviour silently dropped unknown keys inside\n * `fillComponentDefaults` (which walked schema keys, never raw keys), so a\n * typo like `MeshRenderer { material: h }` (singular legacy field name; the\n * current schema has `materials: array<...>`) produced an empty-defaults row\n * + an invisible / mid-grey entity downstream. Surfacing the typo at the\n * spawn boundary collapses a class of \"renders wrong, looks like a graphics\n * bug\" reports into a single explicit error.\n *\n * `.code = 'spawn-data-unknown-field'`\n * `.detail = { component, field, knownFields }`\n * `.hint` — names the offending field and lists the schema's known fields.\n */\nexport class SpawnDataUnknownFieldError extends Error {\n override readonly name = 'SpawnDataUnknownFieldError';\n readonly code = 'spawn-data-unknown-field' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly knownFields: readonly string[];\n };\n\n constructor(componentName: string, fieldName: string, knownFields: readonly string[]) {\n const sortedKnown = [...knownFields].sort();\n const expected = `field name in {${sortedKnown.join(', ')}}`;\n const hint =\n `'${fieldName}' is not a schema field of '${componentName}'. ` +\n `Known fields: ${sortedKnown.join(', ')}. ` +\n `Check for a typo or a stale single-vs-plural rename (e.g. 'material' vs 'materials').`;\n super(\n `${componentName}: spawn data carries unknown field.\\n` +\n ` code: spawn-data-unknown-field\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${fieldName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { component: componentName, field: fieldName, knownFields: sortedKnown };\n }\n}\nexport type QuerySpanUnavailableReason =\n | 'optional-data'\n | 'sparse-component'\n | 'relationship-component';\n\nexport class QueryDescriptorConflictError extends Error {\n override readonly name = 'QueryDescriptorConflictError';\n readonly code = 'query-descriptor-conflict' as const;\n readonly expected = 'each component occupies one descriptor role';\n readonly hint: string;\n readonly detail: { readonly componentName: string; readonly roles: readonly string[] };\n\n constructor(componentName: string, roles: readonly string[]) {\n const hint = `Remove ${componentName} from all but one of: ${roles.join(', ')}.`;\n super(`Query descriptor roles conflict for ${componentName}.\\n hint: ${hint}`);\n this.hint = hint;\n this.detail = { componentName, roles };\n }\n}\n\nexport class QueryDataRequiresFieldsError extends Error {\n override readonly name = 'QueryDataRequiresFieldsError';\n readonly code = 'query-data-requires-fields' as const;\n readonly expected = 'a component with at least one data field';\n readonly hint: string;\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n const hint = `Move tag ${componentName} to with or without.`;\n super(`Query data access requires fields on ${componentName}.\\n hint: ${hint}`);\n this.hint = hint;\n this.detail = { componentName };\n }\n}\n\nexport class QuerySpanUnavailableError extends Error {\n override readonly name = 'QuerySpanUnavailableError';\n readonly code = 'query-span-unavailable' as const;\n readonly expected = 'a descriptor whose rows form contiguous table ranges';\n readonly hint = 'Use row iteration or split the query.';\n readonly detail: { readonly reason: QuerySpanUnavailableReason };\n\n constructor(reason: QuerySpanUnavailableReason) {\n super(`Query spans are unavailable: ${reason}.\\n hint: Use row iteration or split the query.`);\n this.detail = { reason };\n }\n}\n\nexport class QueryIterationInvalidatedError extends Error {\n override readonly name = 'QueryIterationInvalidatedError';\n readonly code = 'query-iteration-invalidated' as const;\n readonly expected: string;\n readonly hint = 'Use deferred Commands for structural mutation, then restart iteration.';\n readonly detail: {\n readonly expectedStructureEpoch: number;\n readonly actualStructureEpoch: number;\n };\n\n constructor(expectedStructureEpoch: number, actualStructureEpoch: number) {\n const expected = `structure epoch ${expectedStructureEpoch}`;\n super(\n `Query iteration was invalidated by structure epoch ${actualStructureEpoch}.\\n hint: ${'Use deferred Commands for structural mutation, then restart iteration.'}`,\n );\n this.expected = expected;\n this.detail = { expectedStructureEpoch, actualStructureEpoch };\n }\n}\n\nexport class QueryIterationActiveError extends Error {\n override readonly name = 'QueryIterationActiveError';\n readonly code = 'query-iteration-active' as const;\n readonly expected = 'one active iterator per Query';\n readonly hint = 'Complete the active iterator or create an independent Query.';\n readonly detail = {};\n\n constructor() {\n super('Query already has an active iterator.\\n hint: Complete it before iterating again.');\n }\n}\n","/**\n * feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —\n * `.code = 'shared-field-invalid-value'`.\n *\n * A `shared<T>` scalar / `array<shared<T>>` element must be a resolved numeric\n * Handle. A raw GUID string / `{ guid }` / `{ kind }` object (the\n * pre-resolution shape a sidecar hands an AI user) used to be silently coerced\n * to the all-zero sentinel by the column packer / scalar write path, so a\n * mis-bound reference read back as `0` / `[0,0,0,0]` and rendered blank with no\n * error. `validateComponentDataKeys` only checks key NAMES, not value types;\n * this error closes the value-type gap at all three write entries\n * (spawn / addComponent / set). `.detail.field` + `.detail.fieldType` name the\n * offending field; `.detail.index` locates the array element (undefined for the\n * scalar form).\n *\n * `.detail = { component, field, fieldType, actualValue, index? }`\n * `.hint` — names the field and points at `AssetRegistry.load + allocSharedRef`.\n */\nexport class SharedFieldInvalidValueError extends Error {\n override readonly name = 'SharedFieldInvalidValueError';\n readonly code = 'shared-field-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n readonly index?: number;\n };\n\n constructor(\n componentName: string,\n fieldName: string,\n fieldType: string,\n actualValue: unknown,\n index?: number,\n ) {\n const at = index === undefined ? '' : `[${index}]`;\n const expected = `a resolved numeric Handle for shared field '${fieldName}${at}'`;\n const hint =\n `'${fieldName}${at}' on '${componentName}' is a ${fieldType} reference; ` +\n `got ${typeof actualValue} (${JSON.stringify(actualValue)}). ` +\n `Resolve the GUID to a handle first: AssetRegistry.load(guid, kind) then allocSharedRef(...), ` +\n `and bind the returned numeric handle — not the raw GUID / sidecar object.`;\n super(\n `${componentName}.${fieldName}${at}: shared field bound to a non-handle value.\\n` +\n ` code: shared-field-invalid-value\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${fieldName}${at}\\n` +\n ` fieldType: ${fieldType}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail =\n index === undefined\n ? { component: componentName, field: fieldName, fieldType, actualValue }\n : { component: componentName, field: fieldName, fieldType, actualValue, index };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —\n// closed-union evolution +3 for the SpriteInstances primitive + tilemap\n// terrain static-batch path. AGENTS.md §Error model evolution contract: minor\n// (add member only).\n//\n// All 3 codes are DECLARED here (M1) but FIRED at the render-system-extract\n// QueryRow loop (M3 w13) — plan-strategy D-6 \"fail-fast at the render\n// domain entry, not at ECS spawn-time (avoids reverse dep ECS -> AssetRegistry\n// to look up MaterialAsset.shadingModel)\". M1 carries class declarations only;\n// the `_routeError` call sites land in M3.\n//\n// Three codes, three failure shapes:\n// - 'sprite-instances-count-mismatch' — transforms.length / 16 !==\n// regions.length / 4 (stride contract; cf. instance-transforms-stride-\n// mismatch which guards Instances stride 16).\n// - 'sprite-instances-requires-sprite-shader' — the entity's MaterialAsset's\n// first pass shader is not 'forgeax::sprite' (extract-time check; AI users\n// using SpriteInstances must pick a sprite-shaded material).\n// - 'sprite-instances-mutually-exclusive-with-instances' — the same entity\n// carries both Instances + SpriteInstances (the two primitives are peers;\n// SpriteInstances supersedes Instances when per-instance UV region is\n// needed).\n//\n// .hint follows charter P3: each contains the literal repair step AI users\n// can paste back into spawn code (transforms/regions stride math; shading\n// model field write; component removal).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Thrown / returned via Layer-3 error route when `SpriteInstances.transforms`\n * (stride 16 — column-major mat4 per instance) and `SpriteInstances.regions`\n * (stride 4 — per-instance UV vec4) instance counts disagree at render-system-\n * extract entry.\n *\n * `.code = 'sprite-instances-count-mismatch'`\n * `.detail = { transformsLength, regionsLength, expectedStride: { transforms: 16, regions: 4 } }`\n * `.hint` — instructs the AI user to enforce\n * `transforms.length / 16 === regions.length / 4`.\n */\nexport class SpriteInstancesCountMismatchError extends Error {\n override readonly name = 'SpriteInstancesCountMismatchError';\n readonly code = 'sprite-instances-count-mismatch' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\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 constructor(transformsLength: number, regionsLength: number) {\n const hint =\n 'SpriteInstances.transforms (stride 16) and SpriteInstances.regions (stride 4) ' +\n 'must describe the same instance count: ensure transforms.length / 16 === regions.length / 4 ' +\n 'at the spawn / set site (resize both arrays together).';\n const expected = 'transforms.length / 16 === regions.length / 4';\n super(\n `SpriteInstances: per-instance count mismatch between transforms and regions.\\n` +\n ` code: sprite-instances-count-mismatch\\n` +\n ` transformsLength: ${transformsLength} (count = ${transformsLength / 16})\\n` +\n ` regionsLength: ${regionsLength} (count = ${regionsLength / 4})\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-count-mismatch',\n transformsLength,\n regionsLength,\n expectedStride: { transforms: 16, regions: 4 },\n };\n }\n}\n\n/**\n * Thrown / returned via Layer-3 error route when an entity carrying\n * `SpriteInstances` references a MaterialAsset whose first pass shader is not\n * `'forgeax::sprite'`. Detected at render-system-extract entry (M3 w13).\n *\n * `.code = 'sprite-instances-requires-sprite-shader'`\n * `.detail = { entityId, observedMaterialShaderId }`\n * `.hint` — instructs the AI user to bind a MaterialAsset whose first pass\n * `shader` is `'forgeax::sprite'` or `'forgeax::sprite-lit'`.\n *\n * feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t6:\n * sprite-lit walks the same per-instance UV region vertex path as sprite\n * (VsOut byte-identical, paramSchema mirror); both shader ids are accepted.\n */\nexport class SpriteInstancesRequiresSpriteShaderError extends Error {\n override readonly name = 'SpriteInstancesRequiresSpriteShaderError';\n readonly code = 'sprite-instances-requires-sprite-shader' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly code: 'sprite-instances-requires-sprite-shader';\n readonly entityId: number;\n readonly observedMaterialShaderId: string;\n };\n\n constructor(entityId: number, observedMaterialShaderId: string) {\n const hint =\n \"bind a MaterialAsset whose first pass `shader` is 'forgeax::sprite' \" +\n \"or 'forgeax::sprite-lit' to this entity's MeshRenderer (SpriteInstances \" +\n 'requires a sprite-family shader so the per-instance UV region is consumed ' +\n 'by the sprite vertex shader path).';\n const expected =\n \"MaterialAsset.passes[0].shader === 'forgeax::sprite' || 'forgeax::sprite-lit'\";\n super(\n `SpriteInstances: entity ${entityId} requires a sprite-shaded MaterialAsset.\\n` +\n ` code: sprite-instances-requires-sprite-shader\\n` +\n ` entityId: ${entityId}\\n` +\n ` observedMaterialShaderId: ${observedMaterialShaderId}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-requires-sprite-shader',\n entityId,\n observedMaterialShaderId,\n };\n }\n}\n\n/**\n * Thrown / returned via Layer-3 error route when the same entity carries both\n * `Instances` (3D per-instance mat4) and `SpriteInstances` (2D per-instance\n * mat4 + UV region). The two primitives are peers — pick one. Detected at\n * render-system-extract entry (M3 w13).\n *\n * `.code = 'sprite-instances-mutually-exclusive-with-instances'`\n * `.detail = { entityId }`\n * `.hint` — instructs the AI user to remove one of the two components.\n */\nexport class SpriteInstancesMutuallyExclusiveWithInstancesError extends Error {\n override readonly name = 'SpriteInstancesMutuallyExclusiveWithInstancesError';\n readonly code = 'sprite-instances-mutually-exclusive-with-instances' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly code: 'sprite-instances-mutually-exclusive-with-instances';\n readonly entityId: number;\n };\n\n constructor(entityId: number) {\n const hint =\n 'remove Instances or replace with SpriteInstances; SpriteInstances supersedes ' +\n 'Instances when per-instance region is needed.';\n const expected = 'entity carries Instances XOR SpriteInstances (not both)';\n super(\n `SpriteInstances: entity ${entityId} carries both Instances and SpriteInstances.\\n` +\n ` code: sprite-instances-mutually-exclusive-with-instances\\n` +\n ` entityId: ${entityId}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-mutually-exclusive-with-instances',\n entityId,\n };\n }\n}\n","import type { Component, ComponentSchema, FieldReflection } from '../component';\nimport { componentDefinition } from '../component-schema';\n\nexport class TimeDeltaInvalidError extends Error {\n override readonly name = 'TimeDeltaInvalidError';\n readonly code = 'time-delta-invalid' as const;\n readonly expected = 'a finite delta greater than or equal to 0';\n readonly hint = 'Call world.update(deltaSeconds) with a finite non-negative delta.';\n readonly detail: { readonly received: number };\n\n constructor(received: number) {\n super(\n `Invalid world.update delta: ${received}.\\n expected: a finite delta greater than or equal to 0\\n hint: Call world.update(deltaSeconds) with a finite non-negative delta.`,\n );\n this.detail = { received };\n }\n}\n\nexport class TimeConfigInvalidError extends Error {\n override readonly name = 'TimeConfigInvalidError';\n readonly code = 'time-config-invalid' as const;\n readonly expected: string;\n readonly hint = 'Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.';\n readonly detail: {\n readonly fixedDeltaSeconds: number;\n readonly maxStepsPerUpdate: number;\n readonly maxDeltaSeconds: number;\n };\n\n constructor(detail: TimeConfigInvalidError['detail']) {\n const expected = 'maxDeltaSeconds >= (maxStepsPerUpdate + 1) * fixedDeltaSeconds';\n super(\n `Invalid World time policy.\\n expected: ${expected}\\n hint: Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.`,\n );\n this.expected = expected;\n this.detail = detail;\n }\n}\n\nexport class ScheduleScopeMismatchError extends Error {\n override readonly name = 'ScheduleScopeMismatchError';\n readonly code = 'schedule-scope-mismatch' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: {\n readonly sourceSchedule: string;\n readonly targetSchedule: string;\n readonly reference?: string;\n };\n\n constructor(sourceSchedule: string, targetSchedule: string, reference?: string) {\n const expected = `a reference owned by ${sourceSchedule}`;\n const hint = `The referenced item belongs to ${targetSchedule}; register and order it in ${sourceSchedule}.`;\n super(`Schedule scope mismatch.\\n expected: ${expected}\\n hint: ${hint}`);\n this.expected = expected;\n this.hint = hint;\n this.detail = { sourceSchedule, targetSchedule, ...(reference ? { reference } : {}) };\n }\n}\n\n/**\n * Returned when a closed enum field receives a value outside its reflected\n * labels. The write owner runs this before any archetype or column mutation.\n */\nexport class ComponentFieldInvalidValueError extends Error {\n override readonly name = 'ComponentFieldInvalidValueError';\n readonly code = 'component-field-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\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 constructor(\n entity: number | undefined,\n component: string,\n field: string,\n received: unknown,\n allowedValues: Readonly<Record<string, number>>,\n ) {\n const entries = Object.entries(allowedValues)\n .map(([label, value]) => `${label}=${value}`)\n .join(', ');\n const expected = `${component}.${field} in { ${entries} }`;\n const hint = `Set ${component}.${field} to one of the reflected enum values: ${entries}`;\n super(\n `${component}.${field} received an invalid enum value.\\n` +\n ` code: component-field-invalid-value\\n` +\n ` component: ${component}\\n` +\n ` field: ${field}\\n` +\n ` received: ${String(received)}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { entity, component, field, received, allowedValues };\n }\n}\n\nexport class ComponentNumericValueInvalidError extends Error {\n override readonly name = 'ComponentNumericValueInvalidError';\n readonly code = 'component-numeric-value-invalid' as const;\n readonly expected = 'a numeric value other than NaN';\n readonly hint: string;\n readonly detail: {\n readonly entity: number | undefined;\n readonly component: string;\n readonly field: string;\n readonly received: number;\n readonly index?: number;\n };\n\n constructor(\n entity: number | undefined,\n component: string,\n field: string,\n received: number,\n index?: number,\n ) {\n const location =\n index === undefined ? `${component}.${field}` : `${component}.${field}[${index}]`;\n const hint = `Replace NaN at ${location} with an authored numeric value; Number.POSITIVE_INFINITY remains valid where the component domain permits it.`;\n super(\n `${location} received NaN.\\n` +\n ` code: component-numeric-value-invalid\\n` +\n ` expected: a numeric value other than NaN\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.detail = {\n entity,\n component,\n field,\n received,\n ...(index === undefined ? {} : { index }),\n };\n }\n}\n\nconst NUMERIC_FIELD_TYPES = new Set(['f32', 'f64', 'i32', 'u32', 'i16', 'u16', 'i8', 'u8', 'enum']);\n\nexport function validateNumericFieldValues<S extends ComponentSchema>(\n component: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n entity?: number,\n): ComponentNumericValueInvalidError | null {\n if (raw === undefined) return null;\n const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;\n const rawValues = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawValues)) {\n const reflection = fields[fieldName];\n if (reflection === undefined) continue;\n const value = rawValues[fieldName];\n if (NUMERIC_FIELD_TYPES.has(reflection.type)) {\n if (typeof value === 'number' && Number.isNaN(value)) {\n return new ComponentNumericValueInvalidError(entity, component.name, fieldName, value);\n }\n continue;\n }\n if (\n reflection.arrayMeta === undefined ||\n !NUMERIC_FIELD_TYPES.has(reflection.arrayMeta.elementType)\n ) {\n continue;\n }\n const length =\n Array.isArray(value) || ArrayBuffer.isView(value)\n ? (value as { readonly length?: number }).length\n : undefined;\n if (length === undefined) continue;\n const values = value as ArrayLike<unknown>;\n for (let index = 0; index < length; index++) {\n const received = values[index];\n if (typeof received === 'number' && Number.isNaN(received)) {\n return new ComponentNumericValueInvalidError(\n entity,\n component.name,\n fieldName,\n received,\n index,\n );\n }\n }\n }\n return null;\n}\n\n/**\n * Returned before an ECS write when a managed `array<T>` field receives a\n * value that the storage boundary cannot interpret as an array payload. The\n * old column writer treated arbitrary objects as an empty payload, which\n * silently changed the row while retaining no evidence of the caller error.\n */\nexport class ManagedArrayInvalidValueError extends Error {\n override readonly name = 'ManagedArrayInvalidValueError';\n readonly code = 'managed-array-invalid-value' as const;\n readonly expected = 'an Array or TypedArray payload (or null/undefined to clear it)';\n readonly hint: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n };\n\n constructor(componentName: string, fieldName: string, fieldType: string, actualValue: unknown) {\n const hint =\n `Set ${componentName}.${fieldName} to a plain array or TypedArray matching ` +\n `${fieldType}; use null or undefined to clear the managed value.`;\n super(\n `${componentName}.${fieldName}: managed array received an invalid value.\\n` +\n ` code: managed-array-invalid-value\\n` +\n ` fieldType: ${fieldType}\\n` +\n ` expected: an Array or TypedArray payload (or null/undefined to clear it)\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.detail = { component: componentName, field: fieldName, fieldType, actualValue };\n }\n}\n\n/**\n * Validate the closed enum fields present in a write payload. Enums without\n * labels remain open numeric fields for compatibility with existing schemas.\n */\nexport function validateEnumFieldValues<S extends ComponentSchema>(\n component: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n entity?: number,\n): ComponentFieldInvalidValueError | null {\n if (raw === undefined) return null;\n const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;\n const rawValues = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawValues)) {\n const reflection = fields[fieldName];\n if (reflection?.type !== 'enum' || reflection.labels === undefined) continue;\n const value = rawValues[fieldName];\n const allowedValues = Object.values(reflection.labels);\n if (typeof value !== 'number' || !Number.isInteger(value) || !allowedValues.includes(value)) {\n return new ComponentFieldInvalidValueError(\n entity,\n component.name,\n fieldName,\n value,\n reflection.labels,\n );\n }\n }\n return null;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260519-light-casters-point-spot-pbr w2 — closed-union evolution +1.\n//\n// Adds 1 new member 'spawn-light-invalid-bounds' to EcsErrorCode (23 -> 24).\n// AGENTS.md section Error model evolution contract: minor (add member only).\n// Triggered by PointLight / SpotLight spawn-time payload validation\n// (plan-strategy D-S3 a). detail.field three-branch\n// ('range' | 'innerOuter' | 'outerNinety') keeps the four bound-violation\n// shapes under one error code so callers narrow first on `.code` then on\n// `.detail.field` (charter P3 progressive disclosure).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` from `world.spawn` when a PointLight or SpotLight\n * payload field is out of the documented bound. Four bound violations share\n * one `.code` and discriminate via `.detail.field`:\n *\n * - `range` — PointLight / SpotLight `range < 0` or `Number.isNaN(range)`.\n * Use `Number.POSITIVE_INFINITY` for an unlimited range or a non-negative\n * meter value.\n * - `innerOuter` — SpotLight `outerConeDeg <= innerConeDeg`. Inner cone is\n * the saturated bright region; outer cone is the falloff edge.\n * - `outerNinety` — SpotLight `outerConeDeg > 90`. KHR_lights_punctual upper\n * bound. A spot light cone wider than 90 degrees becomes a point light;\n * use PointLight instead.\n * - `direction` — DirectionalLight / SpotLight `direction` is missing or a\n * zero vector `[0, 0, 0]`. Direction has no default (there is no universal\n * default direction): omitting it lands the array layer-3 all-zero, which is\n * the same illegal state as an explicit zero vector. Supply a non-zero\n * direction (feat-20260709 M2 / D-1, add-only union member).\n *\n * `.code = 'spawn-light-invalid-bounds'`\n * `.detail.field` is derived from `keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY`;\n * `.detail.got` is `number | readonly number[]`.\n * `.hint` — names the offending field plus the valid replacement form.\n */\nconst SPAWN_LIGHT_INVALID_BOUNDS_POLICY = {\n intensity: {\n expected: 'intensity is finite and >= 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.intensity must be a finite non-negative number (got ${got})`,\n },\n color: {\n expected: 'color is a finite non-negative [r, g, b] vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.color must contain three finite non-negative channels (got ${JSON.stringify(got)})`,\n },\n width: {\n expected: 'width is finite and > 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.width must be a finite positive meter value (got ${got})`,\n },\n height: {\n expected: 'height is finite and > 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.height must be a finite positive meter value (got ${got})`,\n },\n irradiance: {\n expected: 'irradiance is a finite 27-value SH vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.irradiance must contain 27 finite SH values (got ${JSON.stringify(got)})`,\n },\n radius: {\n expected: 'radius is finite and >= R_MIN',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.radius must be a finite value >= R_MIN (got ${got})`,\n },\n range: {\n expected: 'range >= 0 or Number.POSITIVE_INFINITY',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.range = ${got} is invalid; use Number.POSITIVE_INFINITY for unlimited range, or a non-negative meter value`,\n },\n innerOuter: {\n expected: 'outerConeDeg > innerConeDeg',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.outerConeDeg <= innerConeDeg (got ${got}); inner cone is the saturated bright region, outer cone is the falloff edge; outerConeDeg > innerConeDeg required`,\n },\n outerNinety: {\n expected: 'outerConeDeg <= 90 (KHR_lights_punctual upper bound)',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.outerConeDeg = ${got} > 90; a spot light cone wider than 90 degrees becomes a point light; use PointLight instead`,\n },\n direction: {\n expected: 'direction is a non-zero [x, y, z] vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.direction is missing or a zero vector (got ${JSON.stringify(got)}); direction has no default, provide a non-zero direction, e.g. [-0.5, -1, -0.3]`,\n },\n} satisfies Record<\n string,\n {\n readonly expected: string;\n readonly hint: (componentName: string, got: number | readonly number[]) => string;\n }\n>;\n\nexport class SpawnLightInvalidBoundsError extends Error {\n override readonly name = 'SpawnLightInvalidBoundsError';\n readonly code = 'spawn-light-invalid-bounds' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY;\n readonly got: number | readonly number[];\n };\n\n constructor(\n componentName: string,\n field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY,\n got: number | readonly number[],\n ) {\n const policy = SPAWN_LIGHT_INVALID_BOUNDS_POLICY[field];\n const hint = policy.hint(componentName, got);\n const expectedStr = policy.expected;\n super(\n `${componentName}: spawn payload bound violation.\\n` +\n ` code: spawn-light-invalid-bounds\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${field}\\n` +\n ` got: ${got}\\n` +\n ` expected: ${expectedStr}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expectedStr;\n this.detail = { field, got };\n }\n}\n\n/**\n * Returned via `Result.err` from resource-setter helpers (e.g.\n * `setTransparentSortConfig`) when a numeric payload field violates the\n * closed bound declared by the resource contract. The first consumer is\n * `TransparentSortConfig.mode ∈ {0, 1, 2}` (plan-strategy D-4); future\n * resource validators with the same shape reuse this code by routing\n * through `.detail.receivedKey` to disambiguate which resource validator\n * surfaced the failure.\n *\n * Closed-set kebab code consistent with `spawn-light-invalid-bounds`\n * (feat-20260519 / w2); AI users consume via `switch (err.code)` exhaustive\n * narrows + `err.detail.receivedMode` (or `err.detail.receivedKey` /\n * `err.expected`) property access — never string-parse the message.\n *\n * `.code = 'resource-invalid-value'`\n * `.detail = { receivedMode: number; receivedKey?: string }`\n * `.hint` — direct copy-paste recovery (e.g. \"0=layer-z, 1=layer-y,\n * 2=layer-yz\" for the sort-config case).\n * `.expected` — the bound contract literal (e.g. \"mode ∈ {0, 1, 2}\").\n *\n * @reuses RhiError structured shape — same `.code / .expected / .hint /\n * .detail` quadruple AI users consume across rhi + ecs.\n */\nexport class ResourceInvalidValueError extends Error {\n override readonly name = 'ResourceInvalidValueError';\n readonly code = 'resource-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly receivedMode: number; readonly receivedKey?: string };\n\n constructor(\n expected: string,\n hint: string,\n detail: { readonly receivedMode: number; readonly receivedKey?: string },\n ) {\n const keyClause = detail.receivedKey === undefined ? '' : ` key: ${detail.receivedKey}\\n`;\n super(\n `resource: invalid value.\\n` +\n ` code: resource-invalid-value\\n` +\n keyClause +\n ` receivedMode: ${detail.receivedMode}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = detail;\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260521-sprite-atlas-animation M1 T-05 — closed-union evolution +1.\n//\n// Adds 1 new member 'sprite-animation-invalid' to EcsErrorCode (25 -> 26).\n// AGENTS.md §Error model evolution contract: minor (add member only).\n// Same-shape add-only mirror of SpawnLightInvalidBoundsError (feat-20260519\n// w2 line 736-776) and ResourceInvalidValueError (feat-20260520 w13 line\n// 862) — the kebab `'<noun>-invalid-...'` series keeps `switch (err.code)`\n// exhaustive narrows visually consistent (charter P4 consistent abstraction;\n// research F-7 candidate A).\n//\n// Triggered by `spriteAnimationTickSystem` (packages/runtime/src/systems/\n// sprite-animation-tick.ts, landed in M4 T-23) when an entity's\n// `SpriteAnimation` row violates one of two runtime invariants:\n//\n// - field='regions-length' -> `regions.length !== frameCount * 4`\n// - field='frame-duration' -> `frameDuration <= 0`\n//\n// `.detail.field` two-branch (charter P3: AI users branch once on\n// `err.code` and once on `err.detail.field` to reach the recovery hint\n// without parsing the message). Plan-strategy section 2 D-1 binds the\n// detail field shape; M4 T-19 / T-20 / T-21 cover the runtime fail-fast\n// paths end-to-end.\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` from `spriteAnimationTickSystem` (M4 T-23) when\n * an entity's `SpriteAnimation` row violates a runtime invariant.\n * Two invariants share one `.code` and discriminate via `.detail.field`:\n *\n * - `regions-length` — `SpriteAnimation.regions.length !== frameCount * 4`.\n * `regions` packs `[uMin, vMin, uW, vH]` per frame so the length must be\n * exactly `frameCount * 4`. Detail carries the offending `regionsLength`\n * alongside the declared `frameCount` so the hint can spell the exact\n * delta in callsite-friendly numbers.\n * - `frame-duration` — `SpriteAnimation.frameDuration <= 0` (covers both\n * `frameDuration === 0` and `frameDuration < 0`; T-21 binds the negative\n * case to the same arm so AI users handle both via a single\n * `if (err.detail.field === 'frame-duration')` branch — charter P4\n * consistent abstraction).\n *\n * `.code = 'sprite-animation-invalid'`\n * `.detail = { field: 'regions-length', regionsLength, frameCount } |\n * { field: 'frame-duration', frameDuration }`\n *\n * Two top-level detail variants give each `.field` branch its own\n * required sub-field shape so AI users get strong narrowing inside\n * `switch (err.detail.field)` without optional sub-fields bleeding\n * across branches (mirrors `SpawnLightInvalidBoundsError`'s shared\n * `got: number` shape but adapted because regions-length /\n * frame-duration carry different sub-field counts).\n *\n * `.hint` — names the offending invariant plus the valid replacement form.\n */\nexport class SpriteAnimationInvalidError extends Error {\n override readonly name = 'SpriteAnimationInvalidError';\n readonly code = 'sprite-animation-invalid' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail:\n | {\n readonly field: 'regions-length';\n readonly regionsLength: number;\n readonly frameCount: number;\n }\n | {\n readonly field: 'frame-duration';\n readonly frameDuration: number;\n };\n\n private static resolvePolicy(detail: SpriteAnimationInvalidError['detail']): {\n readonly expected: string;\n readonly hint: string;\n } {\n switch (detail.field) {\n case 'regions-length':\n return {\n expected: 'SpriteAnimation.regions.length === frameCount * 4',\n hint: `SpriteAnimation.regions.length = ${detail.regionsLength} does not match frameCount * 4 = ${detail.frameCount * 4}; pack 4 floats [uMin, vMin, uW, vH] per frame (see <name>.atlas.meta.json sidecar 'regions' map)`,\n };\n case 'frame-duration':\n return {\n expected: 'SpriteAnimation.frameDuration > 0',\n hint: `SpriteAnimation.frameDuration = ${detail.frameDuration} is invalid; use a positive seconds-per-frame value (e.g. 0.1 = 10 fps)`,\n };\n }\n }\n\n constructor(detail: SpriteAnimationInvalidError['detail']) {\n const policy = SpriteAnimationInvalidError.resolvePolicy(detail);\n super(\n `SpriteAnimation: invariant violated.\\n` +\n ` code: sprite-animation-invalid\\n` +\n ` field: ${detail.field}\\n` +\n ` expected: ${policy.expected}\\n` +\n ` hint: ${policy.hint}`,\n );\n this.hint = policy.hint;\n this.expected = policy.expected;\n this.detail = detail;\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.\nimport type { Result } from '@forgeax/engine-types';\nimport type { BufferPool } from './buffer-pool';\nimport type { Component, ComponentSchema, ShapeOf } from './component';\nimport type { EntityHandle } from './entity-handle';\nimport type { WorldExecutionFault } from './execution/shared-kernel';\nimport type { ResourceStore } from './resource';\nimport type { Schedule } from './schedule';\nimport type { ScheduleToken } from './schedule-token';\nimport type { SharedRefStore } from './shared-ref-store';\nimport type { Archetype } from './storage/archetype';\nimport type { ArchetypeGraph } from './storage/archetype-graph';\nimport type { ChangeTicks } from './storage/change-detection';\nimport type { StructuralEvidenceRing } from './storage/structural-evidence';\nimport type { Table } from './storage/table';\nimport type { ClockWriter } from './time';\nimport type { ComponentData, EcsError, EntityRecord } from './world';\n\n/** @internal Package-private identity; absent from the public export map. */\nexport const worldInternal: unique symbol = Symbol.for(\n 'forgeax.ecs.worldInternal',\n) as unknown as typeof worldInternal;\n\n/**\n * The one package-internal capability surface owned by World.\n *\n * Every member is explicit so an extraction cannot silently widen the seam or\n * leak an untyped state bag. The symbol itself remains package-private and is\n * the only route used by query, commands, and lifecycle helpers.\n */\n/** @internal Raw ECS owner seam; source-relative consumers only. */\nexport interface WorldInternal {\n readonly allocatePendingEntity: () => EntityHandle;\n readonly cancelPendingEntity: (entity: EntityHandle) => void;\n readonly getArrayView: (\n entity: EntityHandle,\n component: Component,\n fieldName: string,\n ) => ArrayLike<number> | undefined;\n readonly getBufferPool: () => BufferPool;\n readonly getClockWriter: () => ClockWriter;\n readonly getComponentChange: (\n entity: EntityHandle,\n componentId: number,\n ) => ChangeTicks | undefined;\n readonly getComponentMutationEpochs: () => readonly number[];\n readonly getEntityArchetype: (entity: EntityHandle) => Archetype | undefined;\n readonly getFixedAccumulator: () => number;\n readonly getGraph: () => ArchetypeGraph;\n readonly getMutationEpoch: () => number;\n readonly getQueryRow: (\n entity: EntityHandle,\n component: Component,\n ) => Result<Record<string, unknown>, EcsError>;\n readonly getRecords: () => EntityRecord[];\n readonly getRelationshipEpoch: (component: Component) => number;\n readonly getRelationshipTargetEntities: (\n component: Component,\n target: EntityHandle,\n ) => readonly EntityHandle[];\n readonly getResources: () => ResourceStore;\n readonly getSchedule: (token: ScheduleToken) => Schedule | undefined;\n readonly getSchedules: () => ReadonlyMap<ScheduleToken, Schedule>;\n readonly getSharedRefs: () => SharedRefStore;\n readonly getStructureEpoch: () => number;\n readonly getStructuralEvidence: () => StructuralEvidenceRing;\n readonly lookupAlive: (\n entity: EntityHandle,\n operation: string,\n component?: string,\n ) => Result<EntityRecord, EcsError>;\n readonly markComponentChanged: (entity: EntityHandle, componentId: number) => void;\n readonly markComponentRangeChanged: (\n table: Table,\n componentId: number,\n rowStart: number,\n rowCount: number,\n ) => void;\n readonly materializeEntity: (\n entity: EntityHandle,\n componentDatas: ComponentData[],\n ) => Result<void, EcsError>;\n readonly materializePendingEntity: (\n entity: EntityHandle,\n componentDatas: ComponentData[],\n ) => Result<void, EcsError>;\n readonly nextMutationEpoch: () => number;\n readonly poisonExecution: (fault: WorldExecutionFault) => void;\n readonly publishDerivedRange: (\n table: Table,\n componentId: number,\n rowStart: number,\n rowCount: number,\n epoch: number,\n ) => void;\n readonly preflightComponentData: (\n holder: EntityHandle | null,\n componentData: ComponentData,\n pendingEntities?: ReadonlySet<number>,\n unavailableEntities?: ReadonlySet<number>,\n ) => Result<void, EcsError>;\n readonly readRow: <S extends ComponentSchema>(\n archetype: Archetype,\n component: Component<string, S>,\n row: number,\n ) => ShapeOf<S>;\n readonly recordIsLive: (\n record: EntityRecord | undefined,\n generation: number,\n ) => record is EntityRecord;\n readonly routeError: (error: unknown, context?: { readonly systemName: string }) => void;\n readonly restoreMutationEpoch: (epoch: number) => void;\n readonly setFixedAccumulator: (value: number) => void;\n readonly setQueryRow: (\n entity: EntityHandle,\n component: Component,\n value: Record<string, unknown>,\n ) => Result<void, EcsError>;\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 — Entity handle.\n//\n// Encoding: u32 = (generation << 24) | (index & 0xFFFFFF)\n// - index: 24 bits — supports up to 16_777_215 simultaneous entities.\n// - generation: 8 bits — retirement when gen exceeds 255 (gen 255 usable);\n// index permanently retired when bumped generation would reach 256.\n//\n// Key difference from @forgeax/engine-ecs: generation does NOT wrap 255 → 0.\n// When generation would exceed 255 (256), the index is permanently retired\n// from the free list to prevent handle aliasing (D-08).\n//\n// feat-20260623-asset-handle-generation M2 / w4: encodeEntity/decodeEntity/\n// entityIndex/entityGeneration are now thin wrappers over the shared gen-slot\n// codec in @forgeax/engine-types (pack / unpackSlot / unpackGen). The\n// `EntityIndexOverflowError` throw and `ENTITY_NULL_RAW` sentinel stay in ecs\n// (D-1). Constants re-export codec values for backward-compatible names.\n\nimport { MAX_GEN, MAX_SLOT, pack, unpackGen, unpackSlot } from '@forgeax/engine-types';\nimport { EntityIndexOverflowError } from './errors';\n\n/**\n * Branded `number` representing an Entity handle. Stored as a JS `number` but\n * holds the u32 bit pattern (generation << 24) | index.\n *\n * The phantom `__entity` brand prevents accidental mixing with other numbers.\n *\n * Naming: the type-space alias is `EntityHandle` (the branded number that\n * identifies a row). The same-named value-space `Entity` re-exported from the\n * package barrel is the id=0 component token (see `./entity`).\n */\nexport type EntityHandle = number & { readonly __entity: unique symbol };\n\n/** Maximum representable entity index (2^24 - 1 = 16_777_215). Re-exports codec MAX_SLOT. */\nexport const ENTITY_MAX_INDEX = MAX_SLOT;\n\n/** Maximum representable generation (2^8 - 1 = 255). Re-exports codec MAX_GEN. */\nexport const ENTITY_MAX_GENERATION = MAX_GEN;\n\n/**\n * Sentinel u32 value reserved for the \"null entity\" slot in `entity`-typed\n * component fields.\n *\n * The encoding `(gen << 24) | index` yields `0xFFFFFFFF` only at the very\n * last valid (gen=255, index=0xFFFFFF) entity, which retires permanently on\n * its first despawn (D-08). Carving out this single bit pattern as the null\n * sentinel costs at most one slot at the far edge of the entity space.\n *\n * Stored u32 column reads compare against `ENTITY_NULL_RAW` first; the\n * column-level `Entity | null` decode lives in `world.readRow`.\n *\n * Scene-as-World-Blueprint anchor (feat-20260514 w12, R-8 lockdown):\n * This module (`packages/ecs/src/entity.ts`) is the canonical export site\n * for the entity null sentinel. Downstream consumers (M2 instantiate\n * layer 3 fallback for `'entity'`-typed component fields, w22) must import\n * from the package barrel:\n *\n * import { ENTITY_NULL_RAW } from '@forgeax/engine-ecs'\n *\n * The raw `0xFFFFFFFF` literal MUST NOT be duplicated at consumer sites\n * (charter proposition 1: SSOT lives here). The decoded JS-side value is\n * `null` (returned by `world.get(e, C).<entityField>`). Layer 3 default\n * for `'entity'` keyword fields stores `ENTITY_NULL_RAW` into the u32\n * column, which decodes back to `null` on read. ecs-managed-buffer feat\n * export verification (w12 grep): see `packages/ecs/src/index.ts` line\n * re-exporting this constant alongside `ENTITY_MAX_GENERATION /\n * ENTITY_MAX_INDEX`. No add-only fallback re-export is required at this\n * time.\n */\nexport const ENTITY_NULL_RAW = 0xffffffff;\n\n/**\n * Encode (index, generation) into a u32 entity handle.\n *\n * Delegates to shared codec `pack(index, generation)` after ecs-specific\n * overflow validation. The `>>> 0` anti-ToInt32 guard is inherited from the\n * codec (D-7 hard constraint).\n *\n * @throws EntityIndexOverflowError when `index > ENTITY_MAX_INDEX` or `index < 0`.\n */\nexport function encodeEntity(index: number, generation: number): EntityHandle {\n if (index < 0 || index > ENTITY_MAX_INDEX) {\n throw new EntityIndexOverflowError(index);\n }\n return pack(index, generation) as EntityHandle;\n}\n\n/** Decode a u32 entity handle into its (index, generation) pair via shared codec. */\nexport function decodeEntity(entity: EntityHandle): { index: number; generation: number } {\n const e = entity as unknown as number;\n return {\n index: unpackSlot(e),\n generation: unpackGen(e),\n };\n}\n\n/** Extract just the index slot from an entity handle via shared codec. */\nexport function entityIndex(entity: EntityHandle): number {\n return unpackSlot(entity as unknown as number);\n}\n\n/** Extract just the generation slot from an entity handle via shared codec. */\nexport function entityGeneration(entity: EntityHandle): number {\n return unpackGen(entity as unknown as number);\n}\n","// @forgeax/engine-ecs - Layer-3 default-value SSOT helper (feat-20260517-\n// spawn-default-fallback / M1).\n//\n// AI users: this file is the SINGLE PHYSICAL LOCATION of the layer-3\n// `typeDefault(fieldType)` table. Three runtime paths consume it (D-2\n// / plan-strategy §2.1 / AC-05):\n//\n// - `world.spawn(...)` (M2 — t9)\n// - `world.addComponent(...)` (M2 — t9, ComponentData<S> shared)\n//\n// Two-layer split (D-3 / plan-strategy §2.2 — DELIBERATELY NOT MERGED):\n//\n// layer-3 (this file)\n// \"raw value fallback\" — fills missing schema fields with the\n// spawn-data raw shape: 0 / false / ENTITY_NULL_RAW / [] / 0 (slot\n// id placeholder). The output is the input shape `world.spawn`\n// receives (`Partial<ShapeOf<S>>` -> `Record<string, unknown>` with\n// all schema keys present).\n//\n// layer-4 (silent fallback inside writeRow / write{Buffer,Array,\n// UniqueRef}Field)\n// \"column-store value fallback\" — when raw === 0 hits a managed-\n// family arm (string / unique<T> / buffer / buffer<N> / array<T> for\n// T != entity), the column store routes 0 to \"empty slot\" semantics\n// (UniqueRefStore handle 0 / BufferPool slot 0 / array slot\n// length === 0). The two layers are NEVER merged — layer-3 stays\n// pure / unaware of column physics; layer-4 stays inside writeRow\n// where the column instance is in scope.\n//\n// The 14-vocab x default-value table (AC-06 closed table, grep-gate t2\n// keyword \"layer-3 typeDefault table\"):\n//\n// ScalarFieldType (11 arms):\n// f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 -> 0\n// bool -> false\n// enum / ref (legacy scalar) -> 0\n//\n// SchemaVocabKeyword (8 arms; one \"buffer\" + one \"buffer<N>\"; one\n// \"ref<T>\" + one \"handle<T>\"; one \"entity\" + one \"string\"; two array\n// variants — array<T,N> / array<T> — collapse to two table arms:\n// 'string' -> 0 (uniqueRefs handle slot;\n// resolved payload '' on read)\n// 'entity' -> ENTITY_NULL_RAW (NULL_ENTITY u32\n// = 0xffffffff)\n// 'array<entity>' -> [] (THE ONLY array<T> arm with []\n// — entity[] runtime shape is a\n// JS array of Entity, not a\n// BufferPool slot id)\n// 'array<T>' (T != entity) -> 0 (BufferPool slot id; layer-4\n// writeArrayField bottoms out to\n// empty slot — D-2 asymmetric;\n// SceneAsset byte-equivalence,\n// OOS-6 letter)\n// 'array<T, N>' (any T) -> 0 (inline stride-N column,\n// feat-20260602; fallback writes the\n// zeroed row, not a slot id)\n// 'buffer' -> 0 (BufferPool slot id; variable\n// byte capacity)\n// 'buffer<N>' -> 0 (inline stride-N u8 column,\n// feat-20260602; fallback writes the\n// zeroed row, not a slot id)\n// 'unique<T>' -> 0 (UniqueRefStore handle slot)\n// 'handle<T>' -> 0 (unmanaged handle phantom u32;\n// schema-level nullable -> NULL\n// sentinel 0)\n//\n// Brand-class semantics (AC-10 / requirements §A-3 reframe round 2):\n// `handle<T>` and `ref<T>` are SCHEMA-LEVEL nullable. Spawn `data: {}`\n// is legal; layer-3 fills 0 (NULL sentinel for unmanaged handles) /\n// 0 (uniqueRefs handle slot, '' payload). SceneAsset.instantiate\n// produces byte-equivalent column state.\n//\n// Anchors:\n// - requirements §AC-05 (helper SSOT) + §AC-06 (closed 14-vocab table)\n// + §AC-09 (SceneAsset byte-equiv) + §AC-10 (brand-class nullable)\n// - plan-strategy §2.1 (helper file location decision)\n// §2.2 (two-layer split JSDoc declaration)\n// §2.3 (array<T> T!=entity asymmetric raw 0)\n// §3.1 (helper node in component graph)\n// §8.2 (naming rules: fillComponentDefaults / typeDefault)\n// §8.4 (head JSDoc as discovery anchor)\n\nimport type { Component, ComponentSchema } from './component';\nimport { componentSchema } from './component';\nimport { componentDefinition } from './component-schema';\nimport { ENTITY_NULL_RAW } from './entity-handle';\nimport { SpawnDataUnknownFieldError } from './errors';\n\n/**\n * Layer-3 silent default for a single schema field type. Returned when\n * the spawn-data raw input (layer 1) and the owner definition defaults\n * map (layer 2) both omit a known schema field.\n *\n * Pure function — runs once per missing field per (component, spawn /\n * instantiate) call. No `BufferPool` / `UniqueRefStore` / `World`\n * dependency: the helper hands back raw column-shape values (u32 / bool\n * / number array literal); the layer-4 silent fallback inside\n * `writeRow` / `write{Buffer,Array,UniqueRef}Field` turns raw `0` into\n * the live column-store value (unique-ref handle 0 / BufferPool slot\n * id 0 / array slot length 0).\n *\n * Mapping is the same closed table the head-JSDoc table documents.\n *\n * @internal — helper-private; AI users call `fillComponentDefaults`.\n */\nfunction typeDefault(fieldType: string): unknown {\n // bool is the only scalar arm with a non-zero default.\n if (fieldType === 'bool') return false;\n // 'entity' uses the runtime NULL_ENTITY sentinel (0xffffffff).\n if (fieldType === 'entity') return ENTITY_NULL_RAW;\n // 'array<entity>' is the only array<T> arm whose layer-3 default is a\n // JS array literal — entity[] runtime shape is a JS Array<Entity>,\n // not a BufferPool slot id (D-2 asymmetric pivot).\n if (fieldType === 'array<entity>') return [];\n // every other vocab keyword (incl. 'string' / 'unique<T>' / 'handle<T>'\n // / 'buffer' / 'buffer<N>' / 'array<T>' (T!=entity) / 'array<T, N>')\n // and every remaining ScalarFieldType (f* / i* / u* / enum / ref)\n // defaults to numeric 0 at the spawn-data raw surface. Layer-4\n // silent fallback inside writeRow turns 0 into the empty-slot\n // shape on the column-store side when applicable.\n return 0;\n}\n\n/**\n * Fill missing schema fields on a partial spawn-data raw with their\n * layer-2 / layer-3 defaults. Public surface used by:\n *\n * - `World.spawn` (writeRow entry, M2)\n * - `World.addComponent` (writeRow entry, M2)\n *\n * Resolution order (matches scene-instance-container.ts JSDoc):\n *\n * layer 1 — explicit raw value (caller passed `data[field] = v`).\n * Carrier: the raw input itself; not handled by this\n * helper — copied through the `if (key in raw)` branch.\n * SceneAsset.instantiate additionally remaps `entity` /\n * `array<entity>` LocalEntityId values BEFORE handing the raw\n * to the helper (the entity-remap layer is NOT this\n * helper's responsibility).\n *\n * layer 2 — owner definition defaults (declared via\n * `defineComponent(name, schema, { defaults })`). Layer-2\n * defaults beat layer-3.\n *\n * layer 3 — `typeDefault(fieldType)` (this file's private dispatch).\n *\n * Returns a fresh `Record<string, unknown>` carrying every schema\n * field. The output is column-shape raw — the caller's writeRow path\n * walks it field-by-field and applies layer-4 silent fallback when a\n * raw `0` lands on a managed-family arm.\n *\n * Pure: no World / store side effect. Thread-safety irrelevant (single-\n * thread JS engine), but the helper allocates one `Object.create(null)`\n * per call so repeated invocations cannot share a mutable record.\n *\n * @param token Component token (name + schema + optional defaults).\n * @param raw Partial spawn-data raw — caller's `Partial<ShapeOf<S>>`.\n * Keys not present in the schema are passed through\n * unchanged (the spawn write path will emit a write-time\n * error if applicable; helper does not validate keys).\n * @returns Record with every schema field populated by layer-1 /\n * layer-2 / layer-3 defaults in that order.\n */\nexport function fillComponentDefaults<S extends ComponentSchema>(\n token: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n): Record<string, unknown> {\n const schema = componentSchema(token) as Record<string, string>;\n const layer2 = componentDefinition(token).defaults;\n const out: Record<string, unknown> = Object.create(null);\n const rawObj = (raw as Record<string, unknown> | undefined) ?? undefined;\n for (const fieldName of Object.keys(schema)) {\n const fieldType = schema[fieldName];\n if (fieldType === undefined) continue;\n // layer 1 — explicit raw value (caller may pass undefined to mean\n // \"use default\"; mirror the existing scene-instance-container\n // behaviour where the gate is `fieldName in raw`).\n if (rawObj !== undefined && fieldName in rawObj) {\n out[fieldName] = rawObj[fieldName];\n continue;\n }\n // layer 2 — component-level defaults map.\n if (layer2 !== undefined && fieldName in layer2) {\n out[fieldName] = layer2[fieldName];\n continue;\n }\n // layer 3 — TS type defaults (silent — no error code).\n out[fieldName] = typeDefault(fieldType);\n }\n return out;\n}\n\n// Re-export the private dispatch for unit-test introspection (t1\n// keyword pin — every vocab arm gets a one-liner it() block). The\n// helper-private arity is preserved at callers via the\n// `fillComponentDefaults` boundary.\nexport { typeDefault };\n\n/**\n * Validate that every key in `raw` is a declared schema field on `token`.\n *\n * Returned as a `SpawnDataUnknownFieldError` on the FIRST offending key\n * (deterministic for AI users; subsequent unknown keys surface on the next\n * spawn after the first is fixed). Pre-fix the unknown key was silently\n * dropped inside `fillComponentDefaults` (which iterates only schema keys),\n * routing typos like `MeshRenderer { material }` (singular legacy field name)\n * into the empty-default path and producing invisible / mid-grey entities\n * downstream.\n *\n * Pure: no World / store side effect; allocates no closure on the hot path\n * (early-returns null when raw is undefined / empty).\n *\n * Call order at every spawn / addComponent / SceneAsset.instantiate /\n * Commands.spawn site: validate FIRST, then `fillComponentDefaults`. The\n * split keeps `fillComponentDefaults` pure (charter P3 SSOT — a fill helper\n * never validates) while every layer-1 raw key reaches one validator gate.\n *\n * @param token Component token (name + schema).\n * @param raw Caller's `Partial<ShapeOf<S>>` — raw spawn payload.\n * @returns `null` on success; `SpawnDataUnknownFieldError` on the first\n * key not declared in `componentSchema(token)`.\n */\nexport function validateComponentDataKeys<S extends ComponentSchema>(\n token: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n): SpawnDataUnknownFieldError | null {\n if (raw === undefined) return null;\n const schema = componentSchema(token) as Record<string, unknown>;\n const rawObj = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawObj)) {\n if (!(fieldName in schema)) {\n return new SpawnDataUnknownFieldError(token.name, fieldName, Object.keys(schema));\n }\n }\n return null;\n}\n","import type { Result } from '@forgeax/engine-types';\nimport type { Component } from '../component';\nimport { componentId } from '../component';\nimport type { EntityHandle } from '../entity-handle';\nimport type { SharedRefMutationRead } from '../shared-ref-store';\nimport type { StructuralEvidenceRead } from '../storage/structural-evidence';\nimport type { EcsError, World } from '../world';\nimport { worldInternal } from '../world-internal';\n\n// Owner packages that need ECS validation/default semantics consume these\n// helpers through the explicit projection surface. They are intentionally not\n// part of the token-first root barrel.\nexport { fillComponentDefaults } from '../component-default-fallback';\nexport {\n ComponentNotDefinedError,\n InstanceTransformsStrideMismatchError,\n ManagedBufferOutOfBoundsError,\n ResourceInvalidValueError,\n SpawnLightInvalidBoundsError,\n SpriteAnimationInvalidError,\n SpriteInstancesCountMismatchError,\n SpriteInstancesMutuallyExclusiveWithInstancesError,\n SpriteInstancesRequiresSpriteShaderError,\n StaleEntityError,\n} from '../errors';\n\n/** Read producer-owned typed structural facts without inferring from a World scan. */\nexport function readStructuralEvidence(world: World, cursor: number): StructuralEvidenceRead {\n return world[worldInternal].getStructuralEvidence().readAfter(cursor) as StructuralEvidenceRead;\n}\n\nexport interface RenderProjectionComponentRequest {\n readonly component: Component;\n readonly fields: readonly string[];\n}\n\nexport interface RenderProjectionRequest {\n readonly components: readonly RenderProjectionComponentRequest[];\n}\n\nexport interface RenderProjectionSpan {\n readonly length: number;\n readonly fields: Readonly<Record<string, ArrayLike<number>>>;\n}\n\nexport interface RenderProjectionSpans {\n readonly generation: number;\n readonly sharedRefEpoch: number;\n readonly spans: readonly RenderProjectionSpan[];\n}\n\nexport interface RenderChangeBatchOk {\n readonly status: 'ok';\n readonly version: RenderReadVersion;\n readonly world: RenderWorldChanges;\n readonly sharedRefs: SharedRefMutationRead;\n}\n\nexport interface RenderChangeBatchRebuild {\n readonly status: 'rebuild';\n readonly version: RenderReadVersion;\n readonly resync: true;\n readonly reason: 'structure-changed';\n readonly world: RenderWorldChanges;\n readonly sharedRefs: SharedRefMutationRead;\n}\n\nexport interface RenderWorldChanges {\n readonly fromEpoch: number;\n readonly toEpoch: number;\n readonly changedComponentIds: readonly number[];\n}\n\nexport type RenderChangeBatch = RenderChangeBatchOk | RenderChangeBatchRebuild;\n\nexport interface RenderReadLease {\n readonly worldIdentity: string;\n readonly generation: number;\n readChanges(version: RenderReadVersion): RenderChangeBatch;\n querySpans(request: RenderProjectionRequest): RenderProjectionSpans;\n captureVersion(): RenderReadVersion;\n dispose(): void;\n}\n\n/**\n * Read one array field through the render projection boundary without\n * materialising the component object. Render extraction uses this for hot\n * transform/instance columns; the World internals remain owned by ECS.\n */\nexport function readRenderArrayView(\n world: World,\n entity: EntityHandle,\n component: Component,\n fieldName: string,\n): ArrayLike<number> | undefined {\n return world[worldInternal].getArrayView(entity, component, fieldName) as\n | ArrayLike<number>\n | undefined;\n}\n\nexport interface RenderReadVersion {\n readonly mutationEpoch: number;\n readonly structureEpoch: number;\n readonly sharedRefEpoch: number;\n}\n\nfunction readProjectionSpans(\n world: World,\n generation: number,\n request: RenderProjectionRequest,\n): RenderProjectionSpans {\n const queryResult = world.query({ read: request.components.map((entry) => entry.component) });\n if (!queryResult.ok) throw new Error(queryResult.error.message);\n const spansResult = queryResult.value.spans();\n if (!spansResult.ok) throw new Error(spansResult.error.message);\n const spans: RenderProjectionSpan[] = [];\n for (const span of spansResult.value) {\n const fields: Record<string, ArrayLike<number>> = {};\n for (const entry of request.components) {\n const shape = span.get(entry.component) as unknown as Record<string, ArrayLike<number>>;\n for (const fieldName of entry.fields) {\n const field = shape[fieldName];\n if (field === undefined) {\n throw new Error(\n `Render projection field '${entry.component.name}.${fieldName}' is unavailable.`,\n );\n }\n fields[`${entry.component.name}.${fieldName}`] = field;\n if (request.components.length === 1) fields[fieldName] = field;\n }\n }\n spans.push({ length: span.length, fields: Object.freeze(fields) });\n }\n return {\n generation,\n sharedRefEpoch: world[worldInternal].getSharedRefs().getMutationEpoch(),\n spans: Object.freeze(spans),\n };\n}\n\n/** Create the render-owned lease from the ECS projection boundary. */\nexport function createRenderReadLease(world: World, token: object = {}): RenderReadLease {\n void token;\n const sharedRefs = world[worldInternal].getSharedRefs();\n let disposed = false;\n\n const assertLive = (): void => {\n if (disposed) throw new Error('RenderReadLease is disposed.');\n };\n\n const captureVersion = (): RenderReadVersion => {\n return {\n mutationEpoch: world[worldInternal].getMutationEpoch(),\n structureEpoch: world[worldInternal].getStructureEpoch(),\n sharedRefEpoch: sharedRefs.getMutationEpoch(),\n };\n };\n\n return {\n worldIdentity: world.identity,\n get generation(): number {\n return Math.max(1, world[worldInternal].getStructureEpoch());\n },\n captureVersion(): RenderReadVersion {\n assertLive();\n return captureVersion();\n },\n readChanges(start: RenderReadVersion): RenderChangeBatch {\n assertLive();\n const toEpoch = world[worldInternal].getMutationEpoch() as number;\n const componentEpochs = world[\n worldInternal\n ].getComponentMutationEpochs() as readonly number[];\n const changedComponentIds: number[] = [];\n for (let componentId = 0; componentId < componentEpochs.length; componentId += 1) {\n const epoch = componentEpochs[componentId] ?? 0;\n if (epoch > start.mutationEpoch && epoch <= toEpoch) changedComponentIds.push(componentId);\n }\n const worldRead: RenderWorldChanges = {\n fromEpoch: start.mutationEpoch,\n toEpoch,\n changedComponentIds,\n };\n const sharedRead = sharedRefs.readChangesSince(start.sharedRefEpoch);\n const version = captureVersion();\n const structureChanged = start.structureEpoch !== world[worldInternal].getStructureEpoch();\n if (structureChanged) {\n return {\n status: 'rebuild',\n version,\n resync: true,\n reason: 'structure-changed',\n world: worldRead,\n sharedRefs: sharedRead,\n };\n }\n return { status: 'ok', version, world: worldRead, sharedRefs: sharedRead };\n },\n querySpans(request: RenderProjectionRequest): RenderProjectionSpans {\n assertLive();\n return readProjectionSpans(\n world,\n Math.max(1, world[worldInternal].getStructureEpoch()),\n request,\n );\n },\n dispose(): void {\n disposed = true;\n },\n };\n}\n\n/**\n * Publish one owner-derived component value through the component's ordinary\n * version. Numeric consumers observe the same row epoch regardless of which\n * owner computed the value; there is no parallel derived-change vocabulary.\n */\nexport function setDerivedComponent(\n world: World,\n entity: EntityHandle,\n component: Component,\n value: Record<string, unknown>,\n): Result<void, EcsError> {\n const result = world[worldInternal].setQueryRow(entity, component, value);\n if (!result.ok) return result;\n world[worldInternal].markComponentChanged(entity, componentId(component));\n return result;\n}\n\n/** Route an owner-domain error through the World without exposing raw internals. */\nexport function routeWorldError(\n world: World,\n error: unknown,\n context?: { readonly systemName: string },\n): void {\n world[worldInternal].routeError(error, context);\n}\n"]}
1
+ {"version":3,"sources":["../../src/component-schema.ts","../../src/errors/query-and-component-errors.ts","../../src/errors/sprite-and-shared-errors.ts","../../src/errors/validation-errors.ts","../../src/world-internal.ts","../../src/execution/shared-kernel.ts","../../src/errors.ts","../../src/component.ts","../../src/entity-handle.ts","../../src/component-default-fallback.ts","../../src/entity.ts","../../src/storage/change-detection.ts","../../src/projection/state-projection.ts","../../src/projection/index.ts"],"names":["componentId"],"mappings":"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","file":"index.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 * Returned via `Result.err` from `world.removeComponent` when the caller tries\n * to remove an essential (undeletable) component\n * (feat-20260602-archetype-stores-full-packed-entity M1 / w3, plan-strategy\n * D-3). The only essential component today is the id=0 `Entity` component: every\n * archetype carries it unconditionally as the row's own packed handle, so\n * removing it is structurally meaningless. The code name is deliberately\n * generic (`remove-essential-component`, not entity-specific) so a future second\n * essential component reuses it without a rename.\n *\n * `.code = 'remove-essential-component'`\n * `.detail = { componentName }`\n * `.hint` — names the essential component + states it cannot be removed.\n */\nexport class RemoveEssentialComponentError extends Error {\n override readonly name = 'RemoveEssentialComponentError';\n readonly code = 'remove-essential-component' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n const hint = `Component \"${componentName}\" is essential (every entity carries it unconditionally) and cannot be removed. Despawn the entity instead if you want to retire it.`;\n const expected = 'non-essential component';\n super(\n `removeComponent: essential component cannot be removed.\\n` +\n ` code: remove-essential-component\\n` +\n ` component: ${componentName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { componentName };\n }\n}\n\n/**\n * Returned via the `Result` err branch when `instantiate` encounters a\n * SceneAsset entity whose `components` map references a component name that was\n * never passed to `defineComponent`.\n *\n * `.code = 'component-not-defined'`\n * `.detail.name` — the offending component name.\n *\n * Promoting this to a class (rather than a bare object literal) keeps the\n * scene-instantiate failure surface inside the `EcsError` class union, so the\n * documented two-level narrow `cause instanceof EcsError` actually matches it\n * (docs/feedbacks/2026-06-03 §6.2 Tier 4.2). `expected` / `hint` accept\n * per-call overrides because the parent-passthrough (ChildOf) site needs a\n * distinct message from the generic entity-component site.\n */\nexport class ComponentNotDefinedError extends Error {\n override readonly name = 'ComponentNotDefinedError';\n readonly code = 'component-not-defined' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly name: string };\n\n constructor(componentName: string, opts?: { expected?: string; hint?: string }) {\n const expected = opts?.expected ?? `component '${componentName}' defined before instantiate`;\n const hint =\n opts?.hint ??\n `define the component via defineComponent('${componentName}', ...) before instantiating this SceneAsset`;\n super(\n `instantiate: component not defined.\\n` +\n ` code: component-not-defined\\n` +\n ` component: ${componentName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { name: componentName };\n }\n}\n\n/**\n * Returned via `Result.err` from `world.spawn` / `world.addComponent` /\n * `world.instantiateScene` / `Commands.spawn` when the caller-supplied\n * data payload carries a key that is not declared in the target component's\n * schema. The pre-fix behaviour silently dropped unknown keys inside\n * `fillComponentDefaults` (which walked schema keys, never raw keys), so a\n * typo like `MeshRenderer { material: h }` (singular legacy field name; the\n * current schema has `materials: array<...>`) produced an empty-defaults row\n * + an invisible / mid-grey entity downstream. Surfacing the typo at the\n * spawn boundary collapses a class of \"renders wrong, looks like a graphics\n * bug\" reports into a single explicit error.\n *\n * `.code = 'spawn-data-unknown-field'`\n * `.detail = { component, field, knownFields }`\n * `.hint` — names the offending field and lists the schema's known fields.\n */\nexport class SpawnDataUnknownFieldError extends Error {\n override readonly name = 'SpawnDataUnknownFieldError';\n readonly code = 'spawn-data-unknown-field' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly knownFields: readonly string[];\n };\n\n constructor(componentName: string, fieldName: string, knownFields: readonly string[]) {\n const sortedKnown = [...knownFields].sort();\n const expected = `field name in {${sortedKnown.join(', ')}}`;\n const hint =\n `'${fieldName}' is not a schema field of '${componentName}'. ` +\n `Known fields: ${sortedKnown.join(', ')}. ` +\n `Check for a typo or a stale single-vs-plural rename (e.g. 'material' vs 'materials').`;\n super(\n `${componentName}: spawn data carries unknown field.\\n` +\n ` code: spawn-data-unknown-field\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${fieldName}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { component: componentName, field: fieldName, knownFields: sortedKnown };\n }\n}\nexport type QuerySpanUnavailableReason =\n | 'optional-data'\n | 'sparse-component'\n | 'relationship-component';\n\nexport class QueryDescriptorConflictError extends Error {\n override readonly name = 'QueryDescriptorConflictError';\n readonly code = 'query-descriptor-conflict' as const;\n readonly expected = 'each component occupies one descriptor role';\n readonly hint: string;\n readonly detail: { readonly componentName: string; readonly roles: readonly string[] };\n\n constructor(componentName: string, roles: readonly string[]) {\n const hint = `Remove ${componentName} from all but one of: ${roles.join(', ')}.`;\n super(`Query descriptor roles conflict for ${componentName}.\\n hint: ${hint}`);\n this.hint = hint;\n this.detail = { componentName, roles };\n }\n}\n\nexport class QueryDataRequiresFieldsError extends Error {\n override readonly name = 'QueryDataRequiresFieldsError';\n readonly code = 'query-data-requires-fields' as const;\n readonly expected = 'a component with at least one data field';\n readonly hint: string;\n readonly detail: { readonly componentName: string };\n\n constructor(componentName: string) {\n const hint = `Move tag ${componentName} to with or without.`;\n super(`Query data access requires fields on ${componentName}.\\n hint: ${hint}`);\n this.hint = hint;\n this.detail = { componentName };\n }\n}\n\nexport class QuerySpanUnavailableError extends Error {\n override readonly name = 'QuerySpanUnavailableError';\n readonly code = 'query-span-unavailable' as const;\n readonly expected = 'a descriptor whose rows form contiguous table ranges';\n readonly hint = 'Use row iteration or split the query.';\n readonly detail: { readonly reason: QuerySpanUnavailableReason };\n\n constructor(reason: QuerySpanUnavailableReason) {\n super(`Query spans are unavailable: ${reason}.\\n hint: Use row iteration or split the query.`);\n this.detail = { reason };\n }\n}\n\nexport class QueryIterationInvalidatedError extends Error {\n override readonly name = 'QueryIterationInvalidatedError';\n readonly code = 'query-iteration-invalidated' as const;\n readonly expected: string;\n readonly hint = 'Use deferred Commands for structural mutation, then restart iteration.';\n readonly detail: {\n readonly expectedStructureEpoch: number;\n readonly actualStructureEpoch: number;\n };\n\n constructor(expectedStructureEpoch: number, actualStructureEpoch: number) {\n const expected = `structure epoch ${expectedStructureEpoch}`;\n super(\n `Query iteration was invalidated by structure epoch ${actualStructureEpoch}.\\n hint: ${'Use deferred Commands for structural mutation, then restart iteration.'}`,\n );\n this.expected = expected;\n this.detail = { expectedStructureEpoch, actualStructureEpoch };\n }\n}\n\nexport class QueryIterationActiveError extends Error {\n override readonly name = 'QueryIterationActiveError';\n readonly code = 'query-iteration-active' as const;\n readonly expected = 'one active iterator per Query';\n readonly hint = 'Complete the active iterator or create an independent Query.';\n readonly detail = {};\n\n constructor() {\n super('Query already has an active iterator.\\n hint: Complete it before iterating again.');\n }\n}\n","/**\n * feat-20260713-mount-override-component-add-and-shared-ref-round M2 / w9 —\n * `.code = 'shared-field-invalid-value'`.\n *\n * A `shared<T>` scalar / `array<shared<T>>` element must be a resolved numeric\n * Handle. A raw GUID string / `{ guid }` / `{ kind }` object (the\n * pre-resolution shape a sidecar hands an AI user) used to be silently coerced\n * to the all-zero sentinel by the column packer / scalar write path, so a\n * mis-bound reference read back as `0` / `[0,0,0,0]` and rendered blank with no\n * error. `validateComponentDataKeys` only checks key NAMES, not value types;\n * this error closes the value-type gap at all three write entries\n * (spawn / addComponent / set). `.detail.field` + `.detail.fieldType` name the\n * offending field; `.detail.index` locates the array element (undefined for the\n * scalar form).\n *\n * `.detail = { component, field, fieldType, actualValue, index? }`\n * `.hint` — names the field and points at `AssetRegistry.load + allocSharedRef`.\n */\nexport class SharedFieldInvalidValueError extends Error {\n override readonly name = 'SharedFieldInvalidValueError';\n readonly code = 'shared-field-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n readonly index?: number;\n };\n\n constructor(\n componentName: string,\n fieldName: string,\n fieldType: string,\n actualValue: unknown,\n index?: number,\n ) {\n const at = index === undefined ? '' : `[${index}]`;\n const expected = `a resolved numeric Handle for shared field '${fieldName}${at}'`;\n const hint =\n `'${fieldName}${at}' on '${componentName}' is a ${fieldType} reference; ` +\n `got ${typeof actualValue} (${JSON.stringify(actualValue)}). ` +\n `Resolve the GUID to a handle first: AssetRegistry.load(guid, kind) then allocSharedRef(...), ` +\n `and bind the returned numeric handle — not the raw GUID / sidecar object.`;\n super(\n `${componentName}.${fieldName}${at}: shared field bound to a non-handle value.\\n` +\n ` code: shared-field-invalid-value\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${fieldName}${at}\\n` +\n ` fieldType: ${fieldType}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail =\n index === undefined\n ? { component: componentName, field: fieldName, fieldType, actualValue }\n : { component: componentName, field: fieldName, fieldType, actualValue, index };\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260625-sprite-instances-and-tilemap-terrain-static-batch M1 / w2 —\n// closed-union evolution +3 for the SpriteInstances primitive + tilemap\n// terrain static-batch path. AGENTS.md §Error model evolution contract: minor\n// (add member only).\n//\n// All 3 codes are DECLARED here (M1) but FIRED at the render-system-extract\n// QueryRow loop (M3 w13) — plan-strategy D-6 \"fail-fast at the render\n// domain entry, not at ECS spawn-time (avoids reverse dep ECS -> AssetRegistry\n// to look up MaterialAsset.shadingModel)\". M1 carries class declarations only;\n// the `_routeError` call sites land in M3.\n//\n// Three codes, three failure shapes:\n// - 'sprite-instances-count-mismatch' — transforms.length / 16 !==\n// regions.length / 4 (stride contract; cf. instance-transforms-stride-\n// mismatch which guards Instances stride 16).\n// - 'sprite-instances-requires-sprite-shader' — the entity's MaterialAsset's\n// first pass shader is not 'forgeax::sprite' (extract-time check; AI users\n// using SpriteInstances must pick a sprite-shaded material).\n// - 'sprite-instances-mutually-exclusive-with-instances' — the same entity\n// carries both Instances + SpriteInstances (the two primitives are peers;\n// SpriteInstances supersedes Instances when per-instance UV region is\n// needed).\n//\n// .hint follows charter P3: each contains the literal repair step AI users\n// can paste back into spawn code (transforms/regions stride math; shading\n// model field write; component removal).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Thrown / returned via Layer-3 error route when `SpriteInstances.transforms`\n * (stride 16 — column-major mat4 per instance) and `SpriteInstances.regions`\n * (stride 4 — per-instance UV vec4) instance counts disagree at render-system-\n * extract entry.\n *\n * `.code = 'sprite-instances-count-mismatch'`\n * `.detail = { transformsLength, regionsLength, expectedStride: { transforms: 16, regions: 4 } }`\n * `.hint` — instructs the AI user to enforce\n * `transforms.length / 16 === regions.length / 4`.\n */\nexport class SpriteInstancesCountMismatchError extends Error {\n override readonly name = 'SpriteInstancesCountMismatchError';\n readonly code = 'sprite-instances-count-mismatch' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\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 constructor(transformsLength: number, regionsLength: number) {\n const hint =\n 'SpriteInstances.transforms (stride 16) and SpriteInstances.regions (stride 4) ' +\n 'must describe the same instance count: ensure transforms.length / 16 === regions.length / 4 ' +\n 'at the spawn / set site (resize both arrays together).';\n const expected = 'transforms.length / 16 === regions.length / 4';\n super(\n `SpriteInstances: per-instance count mismatch between transforms and regions.\\n` +\n ` code: sprite-instances-count-mismatch\\n` +\n ` transformsLength: ${transformsLength} (count = ${transformsLength / 16})\\n` +\n ` regionsLength: ${regionsLength} (count = ${regionsLength / 4})\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-count-mismatch',\n transformsLength,\n regionsLength,\n expectedStride: { transforms: 16, regions: 4 },\n };\n }\n}\n\n/**\n * Thrown / returned via Layer-3 error route when an entity carrying\n * `SpriteInstances` references a MaterialAsset whose first pass shader is not\n * `'forgeax::sprite'`. Detected at render-system-extract entry (M3 w13).\n *\n * `.code = 'sprite-instances-requires-sprite-shader'`\n * `.detail = { entityId, observedMaterialShaderId }`\n * `.hint` — instructs the AI user to bind a MaterialAsset whose first pass\n * `shader` is `'forgeax::sprite'` or `'forgeax::sprite-lit'`.\n *\n * feat-20260624-sprite-lit-shading-model-pure-2d-lighting M1' / t6:\n * sprite-lit walks the same per-instance UV region vertex path as sprite\n * (VsOut byte-identical, paramSchema mirror); both shader ids are accepted.\n */\nexport class SpriteInstancesRequiresSpriteShaderError extends Error {\n override readonly name = 'SpriteInstancesRequiresSpriteShaderError';\n readonly code = 'sprite-instances-requires-sprite-shader' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly code: 'sprite-instances-requires-sprite-shader';\n readonly entityId: number;\n readonly observedMaterialShaderId: string;\n };\n\n constructor(entityId: number, observedMaterialShaderId: string) {\n const hint =\n \"bind a MaterialAsset whose first pass `shader` is 'forgeax::sprite' \" +\n \"or 'forgeax::sprite-lit' to this entity's MeshRenderer (SpriteInstances \" +\n 'requires a sprite-family shader so the per-instance UV region is consumed ' +\n 'by the sprite vertex shader path).';\n const expected =\n \"MaterialAsset.passes[0].shader === 'forgeax::sprite' || 'forgeax::sprite-lit'\";\n super(\n `SpriteInstances: entity ${entityId} requires a sprite-shaded MaterialAsset.\\n` +\n ` code: sprite-instances-requires-sprite-shader\\n` +\n ` entityId: ${entityId}\\n` +\n ` observedMaterialShaderId: ${observedMaterialShaderId}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-requires-sprite-shader',\n entityId,\n observedMaterialShaderId,\n };\n }\n}\n\n/**\n * Thrown / returned via Layer-3 error route when the same entity carries both\n * `Instances` (3D per-instance mat4) and `SpriteInstances` (2D per-instance\n * mat4 + UV region). The two primitives are peers — pick one. Detected at\n * render-system-extract entry (M3 w13).\n *\n * `.code = 'sprite-instances-mutually-exclusive-with-instances'`\n * `.detail = { entityId }`\n * `.hint` — instructs the AI user to remove one of the two components.\n */\nexport class SpriteInstancesMutuallyExclusiveWithInstancesError extends Error {\n override readonly name = 'SpriteInstancesMutuallyExclusiveWithInstancesError';\n readonly code = 'sprite-instances-mutually-exclusive-with-instances' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly code: 'sprite-instances-mutually-exclusive-with-instances';\n readonly entityId: number;\n };\n\n constructor(entityId: number) {\n const hint =\n 'remove Instances or replace with SpriteInstances; SpriteInstances supersedes ' +\n 'Instances when per-instance region is needed.';\n const expected = 'entity carries Instances XOR SpriteInstances (not both)';\n super(\n `SpriteInstances: entity ${entityId} carries both Instances and SpriteInstances.\\n` +\n ` code: sprite-instances-mutually-exclusive-with-instances\\n` +\n ` entityId: ${entityId}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = {\n code: 'sprite-instances-mutually-exclusive-with-instances',\n entityId,\n };\n }\n}\n","import type { Component, ComponentSchema, FieldReflection } from '../component';\nimport { componentDefinition } from '../component-schema';\n\nexport class TimeDeltaInvalidError extends Error {\n override readonly name = 'TimeDeltaInvalidError';\n readonly code = 'time-delta-invalid' as const;\n readonly expected = 'a finite delta greater than or equal to 0';\n readonly hint = 'Call world.update(deltaSeconds) with a finite non-negative delta.';\n readonly detail: { readonly received: number };\n\n constructor(received: number) {\n super(\n `Invalid world.update delta: ${received}.\\n expected: a finite delta greater than or equal to 0\\n hint: Call world.update(deltaSeconds) with a finite non-negative delta.`,\n );\n this.detail = { received };\n }\n}\n\nexport class TimeConfigInvalidError extends Error {\n override readonly name = 'TimeConfigInvalidError';\n readonly code = 'time-config-invalid' as const;\n readonly expected: string;\n readonly hint = 'Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.';\n readonly detail: {\n readonly fixedDeltaSeconds: number;\n readonly maxStepsPerUpdate: number;\n readonly maxDeltaSeconds: number;\n };\n\n constructor(detail: TimeConfigInvalidError['detail']) {\n const expected = 'maxDeltaSeconds >= (maxStepsPerUpdate + 1) * fixedDeltaSeconds';\n super(\n `Invalid World time policy.\\n expected: ${expected}\\n hint: Increase maxDeltaSeconds or decrease maxStepsPerUpdate or fixedDeltaSeconds.`,\n );\n this.expected = expected;\n this.detail = detail;\n }\n}\n\nexport class ScheduleScopeMismatchError extends Error {\n override readonly name = 'ScheduleScopeMismatchError';\n readonly code = 'schedule-scope-mismatch' as const;\n readonly expected: string;\n readonly hint: string;\n readonly detail: {\n readonly sourceSchedule: string;\n readonly targetSchedule: string;\n readonly reference?: string;\n };\n\n constructor(sourceSchedule: string, targetSchedule: string, reference?: string) {\n const expected = `a reference owned by ${sourceSchedule}`;\n const hint = `The referenced item belongs to ${targetSchedule}; register and order it in ${sourceSchedule}.`;\n super(`Schedule scope mismatch.\\n expected: ${expected}\\n hint: ${hint}`);\n this.expected = expected;\n this.hint = hint;\n this.detail = { sourceSchedule, targetSchedule, ...(reference ? { reference } : {}) };\n }\n}\n\n/**\n * Returned when a closed enum field receives a value outside its reflected\n * labels. The write owner runs this before any archetype or column mutation.\n */\nexport class ComponentFieldInvalidValueError extends Error {\n override readonly name = 'ComponentFieldInvalidValueError';\n readonly code = 'component-field-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\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 constructor(\n entity: number | undefined,\n component: string,\n field: string,\n received: unknown,\n allowedValues: Readonly<Record<string, number>>,\n ) {\n const entries = Object.entries(allowedValues)\n .map(([label, value]) => `${label}=${value}`)\n .join(', ');\n const expected = `${component}.${field} in { ${entries} }`;\n const hint = `Set ${component}.${field} to one of the reflected enum values: ${entries}`;\n super(\n `${component}.${field} received an invalid enum value.\\n` +\n ` code: component-field-invalid-value\\n` +\n ` component: ${component}\\n` +\n ` field: ${field}\\n` +\n ` received: ${String(received)}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = { entity, component, field, received, allowedValues };\n }\n}\n\nexport class ComponentNumericValueInvalidError extends Error {\n override readonly name = 'ComponentNumericValueInvalidError';\n readonly code = 'component-numeric-value-invalid' as const;\n readonly expected = 'a numeric value other than NaN';\n readonly hint: string;\n readonly detail: {\n readonly entity: number | undefined;\n readonly component: string;\n readonly field: string;\n readonly received: number;\n readonly index?: number;\n };\n\n constructor(\n entity: number | undefined,\n component: string,\n field: string,\n received: number,\n index?: number,\n ) {\n const location =\n index === undefined ? `${component}.${field}` : `${component}.${field}[${index}]`;\n const hint = `Replace NaN at ${location} with an authored numeric value; Number.POSITIVE_INFINITY remains valid where the component domain permits it.`;\n super(\n `${location} received NaN.\\n` +\n ` code: component-numeric-value-invalid\\n` +\n ` expected: a numeric value other than NaN\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.detail = {\n entity,\n component,\n field,\n received,\n ...(index === undefined ? {} : { index }),\n };\n }\n}\n\nconst NUMERIC_FIELD_TYPES = new Set(['f32', 'f64', 'i32', 'u32', 'i16', 'u16', 'i8', 'u8', 'enum']);\n\nexport function validateNumericFieldValues<S extends ComponentSchema>(\n component: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n entity?: number,\n): ComponentNumericValueInvalidError | null {\n if (raw === undefined) return null;\n const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;\n const rawValues = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawValues)) {\n const reflection = fields[fieldName];\n if (reflection === undefined) continue;\n const value = rawValues[fieldName];\n if (NUMERIC_FIELD_TYPES.has(reflection.type)) {\n if (typeof value === 'number' && Number.isNaN(value)) {\n return new ComponentNumericValueInvalidError(entity, component.name, fieldName, value);\n }\n continue;\n }\n if (\n reflection.arrayMeta === undefined ||\n !NUMERIC_FIELD_TYPES.has(reflection.arrayMeta.elementType)\n ) {\n continue;\n }\n const length =\n Array.isArray(value) || ArrayBuffer.isView(value)\n ? (value as { readonly length?: number }).length\n : undefined;\n if (length === undefined) continue;\n const values = value as ArrayLike<unknown>;\n for (let index = 0; index < length; index++) {\n const received = values[index];\n if (typeof received === 'number' && Number.isNaN(received)) {\n return new ComponentNumericValueInvalidError(\n entity,\n component.name,\n fieldName,\n received,\n index,\n );\n }\n }\n }\n return null;\n}\n\n/**\n * Returned before an ECS write when a managed `array<T>` field receives a\n * value that the storage boundary cannot interpret as an array payload. The\n * old column writer treated arbitrary objects as an empty payload, which\n * silently changed the row while retaining no evidence of the caller error.\n */\nexport class ManagedArrayInvalidValueError extends Error {\n override readonly name = 'ManagedArrayInvalidValueError';\n readonly code = 'managed-array-invalid-value' as const;\n readonly expected = 'an Array or TypedArray payload (or null/undefined to clear it)';\n readonly hint: string;\n readonly detail: {\n readonly component: string;\n readonly field: string;\n readonly fieldType: string;\n readonly actualValue: unknown;\n };\n\n constructor(componentName: string, fieldName: string, fieldType: string, actualValue: unknown) {\n const hint =\n `Set ${componentName}.${fieldName} to a plain array or TypedArray matching ` +\n `${fieldType}; use null or undefined to clear the managed value.`;\n super(\n `${componentName}.${fieldName}: managed array received an invalid value.\\n` +\n ` code: managed-array-invalid-value\\n` +\n ` fieldType: ${fieldType}\\n` +\n ` expected: an Array or TypedArray payload (or null/undefined to clear it)\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.detail = { component: componentName, field: fieldName, fieldType, actualValue };\n }\n}\n\n/**\n * Validate the closed enum fields present in a write payload. Enums without\n * labels remain open numeric fields for compatibility with existing schemas.\n */\nexport function validateEnumFieldValues<S extends ComponentSchema>(\n component: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n entity?: number,\n): ComponentFieldInvalidValueError | null {\n if (raw === undefined) return null;\n const fields = componentDefinition(component).fields as Readonly<Record<string, FieldReflection>>;\n const rawValues = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawValues)) {\n const reflection = fields[fieldName];\n if (reflection?.type !== 'enum' || reflection.labels === undefined) continue;\n const value = rawValues[fieldName];\n const allowedValues = Object.values(reflection.labels);\n if (typeof value !== 'number' || !Number.isInteger(value) || !allowedValues.includes(value)) {\n return new ComponentFieldInvalidValueError(\n entity,\n component.name,\n fieldName,\n value,\n reflection.labels,\n );\n }\n }\n return null;\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260519-light-casters-point-spot-pbr w2 — closed-union evolution +1.\n//\n// Adds 1 new member 'spawn-light-invalid-bounds' to EcsErrorCode (23 -> 24).\n// AGENTS.md section Error model evolution contract: minor (add member only).\n// Triggered by PointLight / SpotLight spawn-time payload validation\n// (plan-strategy D-S3 a). detail.field three-branch\n// ('range' | 'innerOuter' | 'outerNinety') keeps the four bound-violation\n// shapes under one error code so callers narrow first on `.code` then on\n// `.detail.field` (charter P3 progressive disclosure).\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` from `world.spawn` when a PointLight or SpotLight\n * payload field is out of the documented bound. Four bound violations share\n * one `.code` and discriminate via `.detail.field`:\n *\n * - `range` — PointLight / SpotLight `range < 0` or `Number.isNaN(range)`.\n * Use `Number.POSITIVE_INFINITY` for an unlimited range or a non-negative\n * meter value.\n * - `innerOuter` — SpotLight `outerConeDeg <= innerConeDeg`. Inner cone is\n * the saturated bright region; outer cone is the falloff edge.\n * - `outerNinety` — SpotLight `outerConeDeg > 90`. KHR_lights_punctual upper\n * bound. A spot light cone wider than 90 degrees becomes a point light;\n * use PointLight instead.\n * - `direction` — DirectionalLight / SpotLight `direction` is missing or a\n * zero vector `[0, 0, 0]`. Direction has no default (there is no universal\n * default direction): omitting it lands the array layer-3 all-zero, which is\n * the same illegal state as an explicit zero vector. Supply a non-zero\n * direction (feat-20260709 M2 / D-1, add-only union member).\n *\n * `.code = 'spawn-light-invalid-bounds'`\n * `.detail.field` is derived from `keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY`;\n * `.detail.got` is `number | readonly number[]`.\n * `.hint` — names the offending field plus the valid replacement form.\n */\nconst SPAWN_LIGHT_INVALID_BOUNDS_POLICY = {\n intensity: {\n expected: 'intensity is finite and >= 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.intensity must be a finite non-negative number (got ${got})`,\n },\n color: {\n expected: 'color is a finite non-negative [r, g, b] vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.color must contain three finite non-negative channels (got ${JSON.stringify(got)})`,\n },\n width: {\n expected: 'width is finite and > 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.width must be a finite positive meter value (got ${got})`,\n },\n height: {\n expected: 'height is finite and > 0',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.height must be a finite positive meter value (got ${got})`,\n },\n irradiance: {\n expected: 'irradiance is a finite 27-value SH vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.irradiance must contain 27 finite SH values (got ${JSON.stringify(got)})`,\n },\n radius: {\n expected: 'radius is finite and >= R_MIN',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.radius must be a finite value >= R_MIN (got ${got})`,\n },\n range: {\n expected: 'range >= 0 or Number.POSITIVE_INFINITY',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.range = ${got} is invalid; use Number.POSITIVE_INFINITY for unlimited range, or a non-negative meter value`,\n },\n innerOuter: {\n expected: 'outerConeDeg > innerConeDeg',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.outerConeDeg <= innerConeDeg (got ${got}); inner cone is the saturated bright region, outer cone is the falloff edge; outerConeDeg > innerConeDeg required`,\n },\n outerNinety: {\n expected: 'outerConeDeg <= 90 (KHR_lights_punctual upper bound)',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.outerConeDeg = ${got} > 90; a spot light cone wider than 90 degrees becomes a point light; use PointLight instead`,\n },\n direction: {\n expected: 'direction is a non-zero [x, y, z] vector',\n hint: (componentName: string, got: number | readonly number[]) =>\n `${componentName}.direction is missing or a zero vector (got ${JSON.stringify(got)}); direction has no default, provide a non-zero direction, e.g. [-0.5, -1, -0.3]`,\n },\n} satisfies Record<\n string,\n {\n readonly expected: string;\n readonly hint: (componentName: string, got: number | readonly number[]) => string;\n }\n>;\n\nexport class SpawnLightInvalidBoundsError extends Error {\n override readonly name = 'SpawnLightInvalidBoundsError';\n readonly code = 'spawn-light-invalid-bounds' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: {\n readonly field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY;\n readonly got: number | readonly number[];\n };\n\n constructor(\n componentName: string,\n field: keyof typeof SPAWN_LIGHT_INVALID_BOUNDS_POLICY,\n got: number | readonly number[],\n ) {\n const policy = SPAWN_LIGHT_INVALID_BOUNDS_POLICY[field];\n const hint = policy.hint(componentName, got);\n const expectedStr = policy.expected;\n super(\n `${componentName}: spawn payload bound violation.\\n` +\n ` code: spawn-light-invalid-bounds\\n` +\n ` component: ${componentName}\\n` +\n ` field: ${field}\\n` +\n ` got: ${got}\\n` +\n ` expected: ${expectedStr}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expectedStr;\n this.detail = { field, got };\n }\n}\n\n/**\n * Returned via `Result.err` from resource-setter helpers (e.g.\n * `setTransparentSortConfig`) when a numeric payload field violates the\n * closed bound declared by the resource contract. The first consumer is\n * `TransparentSortConfig.mode ∈ {0, 1, 2}` (plan-strategy D-4); future\n * resource validators with the same shape reuse this code by routing\n * through `.detail.receivedKey` to disambiguate which resource validator\n * surfaced the failure.\n *\n * Closed-set kebab code consistent with `spawn-light-invalid-bounds`\n * (feat-20260519 / w2); AI users consume via `switch (err.code)` exhaustive\n * narrows + `err.detail.receivedMode` (or `err.detail.receivedKey` /\n * `err.expected`) property access — never string-parse the message.\n *\n * `.code = 'resource-invalid-value'`\n * `.detail = { receivedMode: number; receivedKey?: string }`\n * `.hint` — direct copy-paste recovery (e.g. \"0=layer-z, 1=layer-y,\n * 2=layer-yz\" for the sort-config case).\n * `.expected` — the bound contract literal (e.g. \"mode ∈ {0, 1, 2}\").\n *\n * @reuses RhiError structured shape — same `.code / .expected / .hint /\n * .detail` quadruple AI users consume across rhi + ecs.\n */\nexport class ResourceInvalidValueError extends Error {\n override readonly name = 'ResourceInvalidValueError';\n readonly code = 'resource-invalid-value' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail: { readonly receivedMode: number; readonly receivedKey?: string };\n\n constructor(\n expected: string,\n hint: string,\n detail: { readonly receivedMode: number; readonly receivedKey?: string },\n ) {\n const keyClause = detail.receivedKey === undefined ? '' : ` key: ${detail.receivedKey}\\n`;\n super(\n `resource: invalid value.\\n` +\n ` code: resource-invalid-value\\n` +\n keyClause +\n ` receivedMode: ${detail.receivedMode}\\n` +\n ` expected: ${expected}\\n` +\n ` hint: ${hint}`,\n );\n this.hint = hint;\n this.expected = expected;\n this.detail = detail;\n }\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// feat-20260521-sprite-atlas-animation M1 T-05 — closed-union evolution +1.\n//\n// Adds 1 new member 'sprite-animation-invalid' to EcsErrorCode (25 -> 26).\n// AGENTS.md §Error model evolution contract: minor (add member only).\n// Same-shape add-only mirror of SpawnLightInvalidBoundsError (feat-20260519\n// w2 line 736-776) and ResourceInvalidValueError (feat-20260520 w13 line\n// 862) — the kebab `'<noun>-invalid-...'` series keeps `switch (err.code)`\n// exhaustive narrows visually consistent (charter P4 consistent abstraction;\n// research F-7 candidate A).\n//\n// Triggered by `spriteAnimationTickSystem` (packages/runtime/src/systems/\n// sprite-animation-tick.ts, landed in M4 T-23) when an entity's\n// `SpriteAnimation` row violates one of two runtime invariants:\n//\n// - field='regions-length' -> `regions.length !== frameCount * 4`\n// - field='frame-duration' -> `frameDuration <= 0`\n//\n// `.detail.field` two-branch (charter P3: AI users branch once on\n// `err.code` and once on `err.detail.field` to reach the recovery hint\n// without parsing the message). Plan-strategy section 2 D-1 binds the\n// detail field shape; M4 T-19 / T-20 / T-21 cover the runtime fail-fast\n// paths end-to-end.\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returned via `Result.err` from `spriteAnimationTickSystem` (M4 T-23) when\n * an entity's `SpriteAnimation` row violates a runtime invariant.\n * Two invariants share one `.code` and discriminate via `.detail.field`:\n *\n * - `regions-length` — `SpriteAnimation.regions.length !== frameCount * 4`.\n * `regions` packs `[uMin, vMin, uW, vH]` per frame so the length must be\n * exactly `frameCount * 4`. Detail carries the offending `regionsLength`\n * alongside the declared `frameCount` so the hint can spell the exact\n * delta in callsite-friendly numbers.\n * - `frame-duration` — `SpriteAnimation.frameDuration <= 0` (covers both\n * `frameDuration === 0` and `frameDuration < 0`; T-21 binds the negative\n * case to the same arm so AI users handle both via a single\n * `if (err.detail.field === 'frame-duration')` branch — charter P4\n * consistent abstraction).\n *\n * `.code = 'sprite-animation-invalid'`\n * `.detail = { field: 'regions-length', regionsLength, frameCount } |\n * { field: 'frame-duration', frameDuration }`\n *\n * Two top-level detail variants give each `.field` branch its own\n * required sub-field shape so AI users get strong narrowing inside\n * `switch (err.detail.field)` without optional sub-fields bleeding\n * across branches (mirrors `SpawnLightInvalidBoundsError`'s shared\n * `got: number` shape but adapted because regions-length /\n * frame-duration carry different sub-field counts).\n *\n * `.hint` — names the offending invariant plus the valid replacement form.\n */\nexport class SpriteAnimationInvalidError extends Error {\n override readonly name = 'SpriteAnimationInvalidError';\n readonly code = 'sprite-animation-invalid' as const;\n readonly hint: string;\n readonly expected: string;\n readonly detail:\n | {\n readonly field: 'regions-length';\n readonly regionsLength: number;\n readonly frameCount: number;\n }\n | {\n readonly field: 'frame-duration';\n readonly frameDuration: number;\n };\n\n private static resolvePolicy(detail: SpriteAnimationInvalidError['detail']): {\n readonly expected: string;\n readonly hint: string;\n } {\n switch (detail.field) {\n case 'regions-length':\n return {\n expected: 'SpriteAnimation.regions.length === frameCount * 4',\n hint: `SpriteAnimation.regions.length = ${detail.regionsLength} does not match frameCount * 4 = ${detail.frameCount * 4}; pack 4 floats [uMin, vMin, uW, vH] per frame (see <name>.atlas.meta.json sidecar 'regions' map)`,\n };\n case 'frame-duration':\n return {\n expected: 'SpriteAnimation.frameDuration > 0',\n hint: `SpriteAnimation.frameDuration = ${detail.frameDuration} is invalid; use a positive seconds-per-frame value (e.g. 0.1 = 10 fps)`,\n };\n }\n }\n\n constructor(detail: SpriteAnimationInvalidError['detail']) {\n const policy = SpriteAnimationInvalidError.resolvePolicy(detail);\n super(\n `SpriteAnimation: invariant violated.\\n` +\n ` code: sprite-animation-invalid\\n` +\n ` field: ${detail.field}\\n` +\n ` expected: ${policy.expected}\\n` +\n ` hint: ${policy.hint}`,\n );\n this.hint = policy.hint;\n this.expected = policy.expected;\n this.detail = detail;\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.\nimport type { Result } from '@forgeax/engine-types';\nimport type { BufferPool } from './buffer-pool';\nimport type { Component, ComponentSchema, ShapeOf } from './component';\nimport type { EntityHandle } from './entity-handle';\nimport type { WorldExecutionFault } from './execution/shared-kernel';\nimport type { ResourceStore } from './resource';\nimport type { Schedule } from './schedule';\nimport type { ScheduleToken } from './schedule-token';\nimport type { SharedRefStore } from './shared-ref-store';\nimport type { Archetype } from './storage/archetype';\nimport type { ArchetypeGraph } from './storage/archetype-graph';\nimport type { ChangeTicks } from './storage/change-detection';\nimport type { Table } from './storage/table';\nimport type { ClockWriter } from './time';\nimport type { ComponentData, EcsError, EntityRecord } from './world';\n\n/** @internal Package-private identity; absent from the public export map. */\nexport const worldInternal: unique symbol = Symbol.for(\n 'forgeax.ecs.worldInternal',\n) as unknown as typeof worldInternal;\n\n/**\n * The one package-internal capability surface owned by World.\n *\n * Every member is explicit so an extraction cannot silently widen the seam or\n * leak an untyped state bag. The symbol itself remains package-private and is\n * the only route used by query, commands, and lifecycle helpers.\n */\n/** @internal Raw ECS owner seam; source-relative consumers only. */\nexport interface WorldInternal {\n readonly allocatePendingEntity: () => EntityHandle;\n readonly cancelPendingEntity: (entity: EntityHandle) => void;\n readonly getArrayView: (\n entity: EntityHandle,\n component: Component,\n fieldName: string,\n ) => ArrayLike<number> | undefined;\n readonly getBufferPool: () => BufferPool;\n readonly getClockWriter: () => ClockWriter;\n readonly getComponentChange: (\n entity: EntityHandle,\n componentId: number,\n ) => ChangeTicks | undefined;\n readonly getComponentMutationEpochs: () => readonly number[];\n readonly getEntityArchetype: (entity: EntityHandle) => Archetype | undefined;\n readonly getFixedAccumulator: () => number;\n readonly getGraph: () => ArchetypeGraph;\n readonly getMutationEpoch: () => number;\n readonly getQueryRow: (\n entity: EntityHandle,\n component: Component,\n ) => Result<Record<string, unknown>, EcsError>;\n readonly getRecords: () => EntityRecord[];\n readonly getRelationshipEpoch: (component: Component) => number;\n readonly getRelationshipTargetEntities: (\n component: Component,\n target: EntityHandle,\n ) => readonly EntityHandle[];\n readonly getResources: () => ResourceStore;\n readonly getSchedule: (token: ScheduleToken) => Schedule | undefined;\n readonly getSchedules: () => ReadonlyMap<ScheduleToken, Schedule>;\n readonly getSharedRefs: () => SharedRefStore;\n readonly getStructureEpoch: () => number;\n readonly lookupAlive: (\n entity: EntityHandle,\n operation: string,\n component?: string,\n ) => Result<EntityRecord, EcsError>;\n readonly markComponentChanged: (entity: EntityHandle, componentId: number) => void;\n readonly markComponentRangeChanged: (\n table: Table,\n componentId: number,\n rowStart: number,\n rowCount: number,\n ) => void;\n readonly materializeEntity: (\n entity: EntityHandle,\n componentDatas: ComponentData[],\n ) => Result<void, EcsError>;\n readonly materializePendingEntity: (\n entity: EntityHandle,\n componentDatas: ComponentData[],\n ) => Result<void, EcsError>;\n readonly nextMutationEpoch: () => number;\n readonly poisonExecution: (fault: WorldExecutionFault) => void;\n readonly publishDerivedRange: (\n table: Table,\n componentId: number,\n rowStart: number,\n rowCount: number,\n epoch: number,\n ) => void;\n readonly preflightComponentData: (\n holder: EntityHandle | null,\n componentData: ComponentData,\n pendingEntities?: ReadonlySet<number>,\n unavailableEntities?: ReadonlySet<number>,\n ) => Result<void, EcsError>;\n readonly readRow: <S extends ComponentSchema>(\n archetype: Archetype,\n component: Component<string, S>,\n row: number,\n ) => ShapeOf<S>;\n readonly recordIsLive: (\n record: EntityRecord | undefined,\n generation: number,\n ) => record is EntityRecord;\n readonly routeError: (error: unknown, context?: { readonly systemName: string }) => void;\n readonly restoreMutationEpoch: (epoch: number) => void;\n readonly setFixedAccumulator: (value: number) => void;\n readonly setQueryRow: (\n entity: EntityHandle,\n component: Component,\n value: Record<string, unknown>,\n ) => Result<void, EcsError>;\n}\n","import type { Component, SchemaFieldType } from '../component';\nimport { componentId, componentSchema } from '../component';\nimport type { QueryDescriptor, QuerySpan } from '../query/query';\nimport type { SystemHandle } from '../schedule';\nimport { worldInternal } from '../world-internal';\n\nexport const SHARED_KERNEL_EXECUTOR_RESOURCE_KEY = 'SharedKernelExecutor';\n\nexport interface KernelDispatchResult {\n readonly mode: 'forced-inline' | 'shared';\n readonly dispatched: number;\n readonly completed: number;\n readonly waitMs: number;\n}\n\nexport interface KernelDispatchFailure {\n readonly cause: unknown;\n readonly dispatched: number;\n readonly completed: number;\n readonly partialWrite: boolean;\n}\n\nexport interface KernelDispatchSpan {\n readonly queryIndex: number;\n readonly span: QuerySpan;\n}\n\nexport interface SharedKernelExecutor {\n warmup?(kernel: SharedKernelDispatch): void;\n execute(\n kernel: SharedKernelDispatch,\n spans: readonly KernelDispatchSpan[],\n ): KernelDispatchResult | KernelDispatchFailure;\n}\n\nexport function isKernelDispatchFailure(\n value: KernelDispatchResult | KernelDispatchFailure,\n): value is KernelDispatchFailure {\n return 'cause' in value;\n}\n\nexport const SHARED_KERNEL_ELIGIBILITY_REASONS = [\n 'callback-not-module-function',\n 'dom-access',\n 'missing-access-declaration',\n 'descriptor-conflict',\n 'object-field',\n 'span-unavailable',\n] as const;\nexport type SharedKernelEligibilityReason = (typeof SHARED_KERNEL_ELIGIBILITY_REASONS)[number];\n\nexport type WorldExecutionHealth = 'healthy' | 'poisoned';\n\nexport interface WorldExecutionFault {\n readonly code: 'shared-kernel-failed';\n readonly kernelName: string;\n readonly cause: unknown;\n readonly partialWrite: boolean;\n readonly retryable: false;\n}\n\nexport interface WorldExecutionState {\n readonly identity: string;\n readonly health: WorldExecutionHealth;\n readonly fault: WorldExecutionFault | null;\n}\n\nlet nextWorldIdentity = 1;\n\nexport function createWorldIdentity(): string {\n const identity = `world-${nextWorldIdentity}`;\n nextWorldIdentity += 1;\n return identity;\n}\n\nexport function healthyWorldExecutionState(identity: string): WorldExecutionState {\n return Object.freeze({ identity, health: 'healthy', fault: null });\n}\n\nexport function poisonedWorldExecutionState(\n identity: string,\n fault: WorldExecutionFault,\n): WorldExecutionState {\n return Object.freeze({ identity, health: 'poisoned', fault: Object.freeze(fault) });\n}\n\nexport interface SharedKernelDefinition<Qs extends readonly QueryDescriptor[]> {\n readonly name: string;\n readonly queries: Qs;\n readonly run: (spans: readonly QuerySpan[]) => void;\n readonly minimumRows?: number;\n readonly before?: readonly (string | import('../schedule-token').ScheduleToken)[];\n readonly after?: readonly (string | import('../schedule-token').ScheduleToken)[];\n}\n\nexport interface SharedKernelDispatch<\n Qs extends readonly QueryDescriptor[] = readonly QueryDescriptor[],\n> {\n readonly kind: 'shared-kernel';\n readonly moduleUrl: string;\n readonly name: string;\n readonly minimumRows: number;\n readonly queries: Qs;\n readonly run: (spans: readonly QuerySpan[]) => void;\n}\n\nexport interface SharedKernelHandle<\n Qs extends readonly QueryDescriptor[] = readonly QueryDescriptor[],\n> extends SystemHandle<Qs>,\n SharedKernelDispatch<Qs> {}\n\nexport class SharedKernelEligibilityError extends Error {\n readonly code = 'shared-kernel-ineligible' as const;\n readonly expected =\n 'a module-loadable named kernel with one or more numeric QuerySpan read/write declarations';\n readonly hint =\n 'export a named function from the kernel module and use only dense numeric QuerySpan columns';\n readonly detail: { readonly kernelName: string; readonly reason: SharedKernelEligibilityReason };\n\n constructor(kernelName: string, reason: SharedKernelEligibilityReason) {\n super(`Shared kernel \"${kernelName}\" is ineligible: ${reason}.`);\n this.name = 'SharedKernelEligibilityError';\n this.detail = { kernelName, reason };\n }\n}\n\nexport class SharedKernelFailureError extends Error {\n readonly code = 'shared-kernel-failed' as const;\n readonly expected = 'every dispatched shard completes without a possible partial write';\n readonly hint =\n 'do not retry this World; inspect detail.cause and rebuild with a new World identity';\n readonly detail: {\n readonly kernelName: string;\n readonly worldIdentity: string;\n readonly cause: unknown;\n readonly partialWrite: boolean;\n readonly retryable: false;\n };\n\n constructor(kernelName: string, worldIdentity: string, cause: unknown, partialWrite: boolean) {\n super(`Shared kernel \"${kernelName}\" failed; World ${worldIdentity} is poisoned.`);\n this.name = 'SharedKernelFailureError';\n this.detail = { kernelName, worldIdentity, cause, partialWrite, retryable: false };\n }\n}\n\nexport class WorldPoisonedError extends Error {\n readonly code = 'world-poisoned' as const;\n readonly expected = 'World health is healthy before update';\n readonly hint = 'stop scheduling this World and explicitly bootstrap a new World identity';\n readonly detail: { readonly worldIdentity: string; readonly fault: unknown };\n\n constructor(worldIdentity: string, fault: unknown) {\n super(`World ${worldIdentity} is poisoned and cannot update.`);\n this.name = 'WorldPoisonedError';\n this.detail = { worldIdentity, fault };\n }\n}\n\nconst NUMERIC_FIELDS = new Set<SchemaFieldType>([\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\nfunction components(descriptor: QueryDescriptor): readonly Component[] {\n return [\n ...(descriptor.read ?? []),\n ...(descriptor.write ?? []),\n ...(descriptor.optional ?? []),\n ...(descriptor.with ?? []),\n ...(descriptor.without ?? []),\n ...(descriptor.changed ?? []),\n ...(descriptor.added ?? []),\n ];\n}\n\nfunction descriptorReason(descriptor: QueryDescriptor): SharedKernelEligibilityReason | undefined {\n if ((descriptor.read?.length ?? 0) + (descriptor.write?.length ?? 0) === 0) {\n return 'missing-access-declaration';\n }\n if (\n (descriptor.optional?.length ?? 0) > 0 ||\n (descriptor.changed?.length ?? 0) > 0 ||\n (descriptor.added?.length ?? 0) > 0\n ) {\n return 'span-unavailable';\n }\n const seen = new Set<number>();\n for (const component of components(descriptor)) {\n if (seen.has(componentId(component))) return 'descriptor-conflict';\n seen.add(componentId(component));\n if (component.storage === 'sparse') return 'span-unavailable';\n if (Object.values(componentSchema(component)).some((field) => !NUMERIC_FIELDS.has(field))) {\n return 'object-field';\n }\n }\n return undefined;\n}\n\nexport function sharedKernelEligibility(\n moduleUrl: string,\n definition: SharedKernelDefinition<readonly QueryDescriptor[]>,\n): SharedKernelEligibilityReason | undefined {\n try {\n new URL(moduleUrl);\n } catch {\n return 'callback-not-module-function';\n }\n const source = Function.prototype.toString.call(definition.run);\n if (definition.run.name.length === 0 || source.includes('=>')) {\n return 'callback-not-module-function';\n }\n if (/\\b(?:document|window|globalThis|HTMLElement|GPUDevice|AudioContext)\\b/u.test(source)) {\n return 'dom-access';\n }\n for (const query of definition.queries) {\n const reason = descriptorReason(query);\n if (reason !== undefined) return reason;\n }\n return undefined;\n}\n\nexport function defineSharedKernel<const Qs extends readonly QueryDescriptor[]>(\n moduleUrl: string,\n definition: SharedKernelDefinition<Qs>,\n): SharedKernelHandle<Qs> {\n const reason = sharedKernelEligibility(moduleUrl, definition);\n if (reason !== undefined) throw new SharedKernelEligibilityError(definition.name, reason);\n\n const handle: SharedKernelHandle<Qs> = Object.freeze({\n kind: 'shared-kernel' as const,\n moduleUrl,\n name: definition.name,\n queries: definition.queries,\n minimumRows: definition.minimumRows ?? 16_384,\n run: definition.run,\n ...(definition.before !== undefined ? { before: definition.before } : {}),\n ...(definition.after !== undefined ? { after: definition.after } : {}),\n fn: (world: import('../world').World, queries: Parameters<SystemHandle<Qs>['fn']>[1]) => {\n const dispatchSpans: KernelDispatchSpan[] = [];\n for (const [queryIndex, query] of queries.entries()) {\n const result = query.spans();\n if (!result.ok) throw new SharedKernelEligibilityError(definition.name, 'span-unavailable');\n for (const span of result.value) dispatchSpans.push({ queryIndex, span });\n }\n const spans = dispatchSpans.map((entry) => entry.span);\n const totalRows = spans.reduce((sum, span) => sum + span.length, 0);\n try {\n if (\n totalRows < (definition.minimumRows ?? 16_384) ||\n !world.hasResource(SHARED_KERNEL_EXECUTOR_RESOURCE_KEY)\n ) {\n definition.run(spans);\n return;\n }\n const executor = world.getResource<SharedKernelExecutor>(\n SHARED_KERNEL_EXECUTOR_RESOURCE_KEY,\n );\n const result = executor.execute(handle, dispatchSpans);\n if (isKernelDispatchFailure(result)) {\n if (!result.partialWrite) {\n definition.run(spans);\n return;\n }\n world[worldInternal].poisonExecution({\n code: 'shared-kernel-failed',\n kernelName: definition.name,\n cause: result.cause,\n partialWrite: result.partialWrite,\n retryable: false,\n });\n throw new SharedKernelFailureError(\n definition.name,\n world.execution.identity,\n result.cause,\n result.partialWrite,\n );\n }\n } catch (cause) {\n if (world.execution.health !== 'poisoned') {\n world[worldInternal].poisonExecution({\n code: 'shared-kernel-failed',\n kernelName: definition.name,\n cause,\n partialWrite: true,\n retryable: false,\n });\n }\n if (cause instanceof SharedKernelFailureError) throw cause;\n throw new SharedKernelFailureError(definition.name, world.execution.identity, cause, true);\n }\n },\n });\n return handle;\n}\n\nexport type SharedFieldView =\n | Float32Array\n | Float64Array\n | Int32Array\n | Uint32Array\n | Int16Array\n | Uint16Array\n | Int8Array\n | Uint8Array;\n\nexport interface SharedSpanBinding {\n readonly entities: Readonly<Uint32Array>;\n readonly length: number;\n readonly read: Readonly<Record<string, Readonly<Record<string, SharedFieldView>>>>;\n readonly write: Readonly<Record<string, Readonly<Record<string, SharedFieldView>>>>;\n}\n\nfunction sliceFields(\n fields: Readonly<Record<string, SharedFieldView>>,\n start: number,\n end: number,\n): Readonly<Record<string, SharedFieldView>> {\n return Object.fromEntries(\n Object.entries(fields).map(([name, view]) => [name, view.subarray(start, end)]),\n );\n}\n\nexport function bindSharedSpan(\n kernel: SharedKernelDispatch,\n span: QuerySpan,\n queryIndex: number,\n): SharedSpanBinding {\n const descriptor = kernel.queries[queryIndex];\n if (descriptor === undefined) throw new Error(`Missing query descriptor ${queryIndex}.`);\n const read = Object.fromEntries(\n (descriptor.read ?? []).map((component) => [\n component.name,\n span.get(component) as unknown as Record<string, SharedFieldView>,\n ]),\n );\n const write = Object.fromEntries(\n (descriptor.write ?? []).map((component) => [\n component.name,\n span.mut(component) as unknown as Record<string, SharedFieldView>,\n ]),\n );\n return { entities: span.entities, length: span.length, read, write };\n}\n\nexport function splitSharedSpan(\n binding: SharedSpanBinding,\n shardCount: number,\n): readonly SharedSpanBinding[] {\n if (binding.length === 0 || shardCount <= 0) return [];\n const count = Math.min(binding.length, shardCount);\n const shards: SharedSpanBinding[] = [];\n for (let index = 0; index < count; index += 1) {\n const start = Math.floor((binding.length * index) / count);\n const end = Math.floor((binding.length * (index + 1)) / count);\n shards.push({\n entities: binding.entities.subarray(start, end),\n length: end - start,\n read: Object.fromEntries(\n Object.entries(binding.read).map(([component, fields]) => [\n component,\n sliceFields(fields, start, end),\n ]),\n ),\n write: Object.fromEntries(\n Object.entries(binding.write).map(([component, fields]) => [\n component,\n sliceFields(fields, start, end),\n ]),\n ),\n });\n }\n return shards;\n}\n\nexport function isSharedSpan(binding: SharedSpanBinding): boolean {\n if (typeof SharedArrayBuffer === 'undefined') return false;\n if (!(binding.entities.buffer instanceof SharedArrayBuffer)) return false;\n return [...Object.values(binding.read), ...Object.values(binding.write)].every((fields) =>\n Object.values(fields).every((view) => view.buffer instanceof SharedArrayBuffer),\n );\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 — Entity handle.\n//\n// Encoding: u32 = (generation << 24) | (index & 0xFFFFFF)\n// - index: 24 bits — supports up to 16_777_215 simultaneous entities.\n// - generation: 8 bits — retirement when gen exceeds 255 (gen 255 usable);\n// index permanently retired when bumped generation would reach 256.\n//\n// Key difference from @forgeax/engine-ecs: generation does NOT wrap 255 → 0.\n// When generation would exceed 255 (256), the index is permanently retired\n// from the free list to prevent handle aliasing (D-08).\n//\n// feat-20260623-asset-handle-generation M2 / w4: encodeEntity/decodeEntity/\n// entityIndex/entityGeneration are now thin wrappers over the shared gen-slot\n// codec in @forgeax/engine-types (pack / unpackSlot / unpackGen). The\n// `EntityIndexOverflowError` throw and `ENTITY_NULL_RAW` sentinel stay in ecs\n// (D-1). Constants re-export codec values for backward-compatible names.\n\nimport { MAX_GEN, MAX_SLOT, pack, unpackGen, unpackSlot } from '@forgeax/engine-types';\nimport { EntityIndexOverflowError } from './errors';\n\n/**\n * Branded `number` representing an Entity handle. Stored as a JS `number` but\n * holds the u32 bit pattern (generation << 24) | index.\n *\n * The phantom `__entity` brand prevents accidental mixing with other numbers.\n *\n * Naming: the type-space alias is `EntityHandle` (the branded number that\n * identifies a row). The same-named value-space `Entity` re-exported from the\n * package barrel is the id=0 component token (see `./entity`).\n */\nexport type EntityHandle = number & { readonly __entity: unique symbol };\n\n/** Maximum representable entity index (2^24 - 1 = 16_777_215). Re-exports codec MAX_SLOT. */\nexport const ENTITY_MAX_INDEX = MAX_SLOT;\n\n/** Maximum representable generation (2^8 - 1 = 255). Re-exports codec MAX_GEN. */\nexport const ENTITY_MAX_GENERATION = MAX_GEN;\n\n/**\n * Sentinel u32 value reserved for the \"null entity\" slot in `entity`-typed\n * component fields.\n *\n * The encoding `(gen << 24) | index` yields `0xFFFFFFFF` only at the very\n * last valid (gen=255, index=0xFFFFFF) entity, which retires permanently on\n * its first despawn (D-08). Carving out this single bit pattern as the null\n * sentinel costs at most one slot at the far edge of the entity space.\n *\n * Stored u32 column reads compare against `ENTITY_NULL_RAW` first; the\n * column-level `Entity | null` decode lives in `world.readRow`.\n *\n * Scene-as-World-Blueprint anchor (feat-20260514 w12, R-8 lockdown):\n * This module (`packages/ecs/src/entity.ts`) is the canonical export site\n * for the entity null sentinel. Downstream consumers (M2 instantiate\n * layer 3 fallback for `'entity'`-typed component fields, w22) must import\n * from the package barrel:\n *\n * import { ENTITY_NULL_RAW } from '@forgeax/engine-ecs'\n *\n * The raw `0xFFFFFFFF` literal MUST NOT be duplicated at consumer sites\n * (charter proposition 1: SSOT lives here). The decoded JS-side value is\n * `null` (returned by `world.get(e, C).<entityField>`). Layer 3 default\n * for `'entity'` keyword fields stores `ENTITY_NULL_RAW` into the u32\n * column, which decodes back to `null` on read. ecs-managed-buffer feat\n * export verification (w12 grep): see `packages/ecs/src/index.ts` line\n * re-exporting this constant alongside `ENTITY_MAX_GENERATION /\n * ENTITY_MAX_INDEX`. No add-only fallback re-export is required at this\n * time.\n */\nexport const ENTITY_NULL_RAW = 0xffffffff;\n\n/**\n * Encode (index, generation) into a u32 entity handle.\n *\n * Delegates to shared codec `pack(index, generation)` after ecs-specific\n * overflow validation. The `>>> 0` anti-ToInt32 guard is inherited from the\n * codec (D-7 hard constraint).\n *\n * @throws EntityIndexOverflowError when `index > ENTITY_MAX_INDEX` or `index < 0`.\n */\nexport function encodeEntity(index: number, generation: number): EntityHandle {\n if (index < 0 || index > ENTITY_MAX_INDEX) {\n throw new EntityIndexOverflowError(index);\n }\n return pack(index, generation) as EntityHandle;\n}\n\n/** Decode a u32 entity handle into its (index, generation) pair via shared codec. */\nexport function decodeEntity(entity: EntityHandle): { index: number; generation: number } {\n const e = entity as unknown as number;\n return {\n index: unpackSlot(e),\n generation: unpackGen(e),\n };\n}\n\n/** Extract just the index slot from an entity handle via shared codec. */\nexport function entityIndex(entity: EntityHandle): number {\n return unpackSlot(entity as unknown as number);\n}\n\n/** Extract just the generation slot from an entity handle via shared codec. */\nexport function entityGeneration(entity: EntityHandle): number {\n return unpackGen(entity as unknown as number);\n}\n","// @forgeax/engine-ecs - Layer-3 default-value SSOT helper (feat-20260517-\n// spawn-default-fallback / M1).\n//\n// AI users: this file is the SINGLE PHYSICAL LOCATION of the layer-3\n// `typeDefault(fieldType)` table. Three runtime paths consume it (D-2\n// / plan-strategy §2.1 / AC-05):\n//\n// - `world.spawn(...)` (M2 — t9)\n// - `world.addComponent(...)` (M2 — t9, ComponentData<S> shared)\n//\n// Two-layer split (D-3 / plan-strategy §2.2 — DELIBERATELY NOT MERGED):\n//\n// layer-3 (this file)\n// \"raw value fallback\" — fills missing schema fields with the\n// spawn-data raw shape: 0 / false / ENTITY_NULL_RAW / [] / 0 (slot\n// id placeholder). The output is the input shape `world.spawn`\n// receives (`Partial<ShapeOf<S>>` -> `Record<string, unknown>` with\n// all schema keys present).\n//\n// layer-4 (silent fallback inside writeRow / write{Buffer,Array,\n// UniqueRef}Field)\n// \"column-store value fallback\" — when raw === 0 hits a managed-\n// family arm (string / unique<T> / buffer / buffer<N> / array<T> for\n// T != entity), the column store routes 0 to \"empty slot\" semantics\n// (UniqueRefStore handle 0 / BufferPool slot 0 / array slot\n// length === 0). The two layers are NEVER merged — layer-3 stays\n// pure / unaware of column physics; layer-4 stays inside writeRow\n// where the column instance is in scope.\n//\n// The 14-vocab x default-value table (AC-06 closed table, grep-gate t2\n// keyword \"layer-3 typeDefault table\"):\n//\n// ScalarFieldType (11 arms):\n// f32 / f64 / i32 / u32 / i16 / u16 / i8 / u8 -> 0\n// bool -> false\n// enum / ref (legacy scalar) -> 0\n//\n// SchemaVocabKeyword (8 arms; one \"buffer\" + one \"buffer<N>\"; one\n// \"ref<T>\" + one \"handle<T>\"; one \"entity\" + one \"string\"; two array\n// variants — array<T,N> / array<T> — collapse to two table arms:\n// 'string' -> 0 (uniqueRefs handle slot;\n// resolved payload '' on read)\n// 'entity' -> ENTITY_NULL_RAW (NULL_ENTITY u32\n// = 0xffffffff)\n// 'array<entity>' -> [] (THE ONLY array<T> arm with []\n// — entity[] runtime shape is a\n// JS array of Entity, not a\n// BufferPool slot id)\n// 'array<T>' (T != entity) -> 0 (BufferPool slot id; layer-4\n// writeArrayField bottoms out to\n// empty slot — D-2 asymmetric;\n// SceneAsset byte-equivalence,\n// OOS-6 letter)\n// 'array<T, N>' (any T) -> 0 (inline stride-N column,\n// feat-20260602; fallback writes the\n// zeroed row, not a slot id)\n// 'buffer' -> 0 (BufferPool slot id; variable\n// byte capacity)\n// 'buffer<N>' -> 0 (inline stride-N u8 column,\n// feat-20260602; fallback writes the\n// zeroed row, not a slot id)\n// 'unique<T>' -> 0 (UniqueRefStore handle slot)\n// 'handle<T>' -> 0 (unmanaged handle phantom u32;\n// schema-level nullable -> NULL\n// sentinel 0)\n//\n// Brand-class semantics (AC-10 / requirements §A-3 reframe round 2):\n// `handle<T>` and `ref<T>` are SCHEMA-LEVEL nullable. Spawn `data: {}`\n// is legal; layer-3 fills 0 (NULL sentinel for unmanaged handles) /\n// 0 (uniqueRefs handle slot, '' payload). SceneAsset.instantiate\n// produces byte-equivalent column state.\n//\n// Anchors:\n// - requirements §AC-05 (helper SSOT) + §AC-06 (closed 14-vocab table)\n// + §AC-09 (SceneAsset byte-equiv) + §AC-10 (brand-class nullable)\n// - plan-strategy §2.1 (helper file location decision)\n// §2.2 (two-layer split JSDoc declaration)\n// §2.3 (array<T> T!=entity asymmetric raw 0)\n// §3.1 (helper node in component graph)\n// §8.2 (naming rules: fillComponentDefaults / typeDefault)\n// §8.4 (head JSDoc as discovery anchor)\n\nimport type { Component, ComponentSchema } from './component';\nimport { componentSchema } from './component';\nimport { componentDefinition } from './component-schema';\nimport { ENTITY_NULL_RAW } from './entity-handle';\nimport { SpawnDataUnknownFieldError } from './errors';\n\n/**\n * Layer-3 silent default for a single schema field type. Returned when\n * the spawn-data raw input (layer 1) and the owner definition defaults\n * map (layer 2) both omit a known schema field.\n *\n * Pure function — runs once per missing field per (component, spawn /\n * instantiate) call. No `BufferPool` / `UniqueRefStore` / `World`\n * dependency: the helper hands back raw column-shape values (u32 / bool\n * / number array literal); the layer-4 silent fallback inside\n * `writeRow` / `write{Buffer,Array,UniqueRef}Field` turns raw `0` into\n * the live column-store value (unique-ref handle 0 / BufferPool slot\n * id 0 / array slot length 0).\n *\n * Mapping is the same closed table the head-JSDoc table documents.\n *\n * @internal — helper-private; AI users call `fillComponentDefaults`.\n */\nfunction typeDefault(fieldType: string): unknown {\n // bool is the only scalar arm with a non-zero default.\n if (fieldType === 'bool') return false;\n // 'entity' uses the runtime NULL_ENTITY sentinel (0xffffffff).\n if (fieldType === 'entity') return ENTITY_NULL_RAW;\n // 'array<entity>' is the only array<T> arm whose layer-3 default is a\n // JS array literal — entity[] runtime shape is a JS Array<Entity>,\n // not a BufferPool slot id (D-2 asymmetric pivot).\n if (fieldType === 'array<entity>') return [];\n // every other vocab keyword (incl. 'string' / 'unique<T>' / 'handle<T>'\n // / 'buffer' / 'buffer<N>' / 'array<T>' (T!=entity) / 'array<T, N>')\n // and every remaining ScalarFieldType (f* / i* / u* / enum / ref)\n // defaults to numeric 0 at the spawn-data raw surface. Layer-4\n // silent fallback inside writeRow turns 0 into the empty-slot\n // shape on the column-store side when applicable.\n return 0;\n}\n\n/**\n * Fill missing schema fields on a partial spawn-data raw with their\n * layer-2 / layer-3 defaults. Public surface used by:\n *\n * - `World.spawn` (writeRow entry, M2)\n * - `World.addComponent` (writeRow entry, M2)\n *\n * Resolution order (matches scene-instance-container.ts JSDoc):\n *\n * layer 1 — explicit raw value (caller passed `data[field] = v`).\n * Carrier: the raw input itself; not handled by this\n * helper — copied through the `if (key in raw)` branch.\n * SceneAsset.instantiate additionally remaps `entity` /\n * `array<entity>` LocalEntityId values BEFORE handing the raw\n * to the helper (the entity-remap layer is NOT this\n * helper's responsibility).\n *\n * layer 2 — owner definition defaults (declared via\n * `defineComponent(name, schema, { defaults })`). Layer-2\n * defaults beat layer-3.\n *\n * layer 3 — `typeDefault(fieldType)` (this file's private dispatch).\n *\n * Returns a fresh `Record<string, unknown>` carrying every schema\n * field. The output is column-shape raw — the caller's writeRow path\n * walks it field-by-field and applies layer-4 silent fallback when a\n * raw `0` lands on a managed-family arm.\n *\n * Pure: no World / store side effect. Thread-safety irrelevant (single-\n * thread JS engine), but the helper allocates one `Object.create(null)`\n * per call so repeated invocations cannot share a mutable record.\n *\n * @param token Component token (name + schema + optional defaults).\n * @param raw Partial spawn-data raw — caller's `Partial<ShapeOf<S>>`.\n * Keys not present in the schema are passed through\n * unchanged (the spawn write path will emit a write-time\n * error if applicable; helper does not validate keys).\n * @returns Record with every schema field populated by layer-1 /\n * layer-2 / layer-3 defaults in that order.\n */\nexport function fillComponentDefaults<S extends ComponentSchema>(\n token: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n): Record<string, unknown> {\n const schema = componentSchema(token) as Record<string, string>;\n const layer2 = componentDefinition(token).defaults;\n const out: Record<string, unknown> = Object.create(null);\n const rawObj = (raw as Record<string, unknown> | undefined) ?? undefined;\n for (const fieldName of Object.keys(schema)) {\n const fieldType = schema[fieldName];\n if (fieldType === undefined) continue;\n // layer 1 — explicit raw value (caller may pass undefined to mean\n // \"use default\"; mirror the existing scene-instance-container\n // behaviour where the gate is `fieldName in raw`).\n if (rawObj !== undefined && fieldName in rawObj) {\n out[fieldName] = rawObj[fieldName];\n continue;\n }\n // layer 2 — component-level defaults map.\n if (layer2 !== undefined && fieldName in layer2) {\n out[fieldName] = layer2[fieldName];\n continue;\n }\n // layer 3 — TS type defaults (silent — no error code).\n out[fieldName] = typeDefault(fieldType);\n }\n return out;\n}\n\n// Re-export the private dispatch for unit-test introspection (t1\n// keyword pin — every vocab arm gets a one-liner it() block). The\n// helper-private arity is preserved at callers via the\n// `fillComponentDefaults` boundary.\nexport { typeDefault };\n\n/**\n * Validate that every key in `raw` is a declared schema field on `token`.\n *\n * Returned as a `SpawnDataUnknownFieldError` on the FIRST offending key\n * (deterministic for AI users; subsequent unknown keys surface on the next\n * spawn after the first is fixed). Pre-fix the unknown key was silently\n * dropped inside `fillComponentDefaults` (which iterates only schema keys),\n * routing typos like `MeshRenderer { material }` (singular legacy field name)\n * into the empty-default path and producing invisible / mid-grey entities\n * downstream.\n *\n * Pure: no World / store side effect; allocates no closure on the hot path\n * (early-returns null when raw is undefined / empty).\n *\n * Call order at every spawn / addComponent / SceneAsset.instantiate /\n * Commands.spawn site: validate FIRST, then `fillComponentDefaults`. The\n * split keeps `fillComponentDefaults` pure (charter P3 SSOT — a fill helper\n * never validates) while every layer-1 raw key reaches one validator gate.\n *\n * @param token Component token (name + schema).\n * @param raw Caller's `Partial<ShapeOf<S>>` — raw spawn payload.\n * @returns `null` on success; `SpawnDataUnknownFieldError` on the first\n * key not declared in `componentSchema(token)`.\n */\nexport function validateComponentDataKeys<S extends ComponentSchema>(\n token: Component<string, S>,\n raw: Partial<Record<string, unknown>> | undefined,\n): SpawnDataUnknownFieldError | null {\n if (raw === undefined) return null;\n const schema = componentSchema(token) as Record<string, unknown>;\n const rawObj = raw as Record<string, unknown>;\n for (const fieldName of Object.keys(rawObj)) {\n if (!(fieldName in schema)) {\n return new SpawnDataUnknownFieldError(token.name, fieldName, Object.keys(schema));\n }\n }\n return null;\n}\n","// @forgeax/engine-ecs - Entity component (feat-20260602-archetype-stores-full-packed-entity M1 / w1).\n//\n// Entity identity is modelled as a real id=0 ECS component `Entity`, whose sole\n// field `self` stores the full 32-bit packed entity handle (generation << 24 |\n// index). Every archetype carries this column unconditionally (it is essential\n// and cannot be removed), so an entity's own handle is read through the exact\n// same query / read path as Transform / MeshRenderer columns:\n//\n// const query = world.query({ read: [Transform] }).unwrap();\n// for (const row of query) row.entity;\n// world.get(e, Entity).unwrap().self === e\n//\n// This retires the \"entity is an archetype side-array + three-step rebuild\"\n// implementation detail (index slot -> generation lookup -> encodeEntity) in\n// favour of a single uniform column read.\n//\n// id=0 structural guarantee: `Entity` MUST be the first `defineComponent`\n// evaluated in the process so the owner identity assigns it 0. The package\n// barrel (packages/ecs/src/index.ts) force-evaluates this module before any\n// other component-defining module and asserts the invariant fail-fast.\n// See plan-strategy D-1 / D-6b (LP-1): the hard-coded 0 + barrel forced\n// registration + startup throw is the locked design; no UECS-style runtime\n// token lookup is introduced (the hot-path archetype column key stays a numeric\n// owner-assigned component identity).\n//\n// charter mapping: P4 (consistent abstraction -- reading the entity handle is\n// reading any other column) + P3 (id=0 drift surfaces as a structured startup\n// throw, never a silent runtime mis-id) + P1 (single top-level import surface).\n\nimport type { ComponentId } from './component';\nimport { componentId, defineComponent } from './component';\n\n/**\n * The id=0 essential `Entity` component. Its single `self` field carries the\n * full packed entity handle for the row it sits on (written at spawn time).\n *\n * Naming convention (feat-20260611-ecs-storage-naming-ssot):\n *\n * - Value-space `Entity` (this const) is the id=0 component token. It is\n * looked up by name (`'Entity'`) at component-registration time and used\n * as a value (`world.spawn`, `world.get(entity, Entity)`).\n * - Type-space `EntityHandle` (the branded number, exported from `../entity`)\n * is the row-identifier handle type. It is used in `: EntityHandle`\n * annotations.\n *\n * The two no longer share a name -- the prior intentional coexistence (a\n * single `Entity` symbol carrying both meanings via TS namespace merging) was\n * dropped because two-roles-one-name created repeated AI-user confusion when\n * reading `: Entity` annotations (is this the handle or the token?). The id=0\n * component token's name (the literal string `'Entity'`) is preserved for\n * runtime stability; only the type-space alias was renamed.\n *\n * @example Read an entity's own handle through a query:\n * const query = world.query({ read: [Transform] }).unwrap();\n * for (const row of query) {\n * const handle = row.entity;\n * }\n *\n * @example Use `world.get` as a general liveness probe:\n * const r = world.get(e, Entity);\n * if (!r.ok) { // r.error.code === 'stale-entity' for a despawned handle\n * }\n */\nexport const Entity = defineComponent('Entity', {\n // Layer-2 default is never observed: `world.spawn` always overwrites `self`\n // with the freshly encoded handle for the row. `null` is the type-correct\n // \"no handle yet\" placeholder (`'entity'` decodes to `EntityHandle | null`).\n self: { type: 'entity', default: null },\n});\n\n/**\n * Marks an entity as disabled.\n *\n * Queries exclude this tag by default. Add `Disabled` to a query's `with`\n * tuple when the query must inspect or re-enable disabled entities.\n */\nexport const Disabled = defineComponent('Disabled', {});\n\n/**\n * SSOT for component ids that are essential to every archetype. Every archetype\n * carries the columns named by these ids unconditionally (they cannot be added\n * or removed via `addComponent` / `removeComponent`). Currently only `Entity`\n * is essential -- the row-identity column that lets every entity read its own\n * packed handle (`world.get(e, Entity).self === e`) through the same column\n * path as any other component (feat-20260602 / charter P4).\n *\n * Frozen so consumers cannot mutate the SSOT. The barrel\n * (`packages/ecs/src/index.ts`) re-exports this constant so the AI-facing\n * `import { ESSENTIAL_COMPONENT_IDS } from '@forgeax/engine-ecs'` works.\n *\n * Physical location is `packages/ecs/src/entity.ts` -- the module that owns\n * the `defineComponent('Entity', ...)` call, so reading the owner identity\n * immediately after registration is well-defined. tweak-20260612-ecs-concept-\n * compression lifted this file back from the historical `components/entity.ts`\n * after `entity-handle.ts` freed up the `entity.ts` slot.\n */\nexport const ESSENTIAL_COMPONENT_IDS: ReadonlyArray<ComponentId> = Object.freeze([\n componentId(Entity),\n]);\n\n/**\n * Fold the essential component ids (currently `[componentId(Entity)]`) into a caller-\n * supplied id list. Returns a NEW array; never mutates the input.\n *\n * - If `ids` already contains every essential id, returns a deduped copy\n * (idempotent under repeated folding).\n * - Otherwise, returns `[...essential, ...ids]` with duplicates of the\n * essential ids removed.\n *\n * The empty input maps to `[componentId(Entity)]` -- the bare-archetype shape that a\n * `world.spawn()` (no components) materialises.\n *\n * Single SSOT consumed by both `archetypeKey` (string-key fold) and\n * `createArchetype` (column-build fold) so the two sites can never disagree\n * on which ids are essential. Hot-path correctness is on `createArchetype`'s\n * side: misalignment between key and columns silently drops fields.\n *\n * @example\n * foldEssentials([2, 5, 7]) // [componentId(Entity), 2, 5, 7]\n * foldEssentials([componentId(Entity), 2, 5]) // [componentId(Entity), 2, 5]\n * foldEssentials([componentId(Entity), componentId(Entity)]) // [componentId(Entity)]\n * foldEssentials([]) // [componentId(Entity)]\n */\nexport function foldEssentials(ids: ReadonlyArray<ComponentId>): ComponentId[] {\n const seen = new Set<ComponentId>();\n const out: ComponentId[] = [];\n for (const essential of ESSENTIAL_COMPONENT_IDS) {\n if (!seen.has(essential)) {\n seen.add(essential);\n out.push(essential);\n }\n }\n for (const id of ids) {\n if (!seen.has(id)) {\n seen.add(id);\n out.push(id);\n }\n }\n return out;\n}\n","// @forgeax/engine-ecs — per-entity and per-resource change ticks.\n\nimport type { Component } from '../component';\nimport * as componentOwner from '../component';\nimport { type EntityHandle, entityIndex } from '../entity-handle';\n\nimport type { ArchetypeGraph } from './archetype-graph';\nimport { getOrCreateSparseTagSet } from './archetype-graph';\n\nexport const PROJECTION_BLOCK_SIZE = 256;\n\nconst INITIAL_SPARSE_CAPACITY = 64;\n\nexport interface ComponentEpochColumns {\n added: Float64Array;\n changed: Float64Array;\n blocks: Float64Array;\n}\n\nexport function createComponentEpochColumns(capacity: number): ComponentEpochColumns {\n return {\n added: new Float64Array(capacity),\n changed: new Float64Array(capacity),\n blocks: new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE)),\n };\n}\n\nexport function growComponentEpochColumns(\n columns: ComponentEpochColumns,\n capacity: number,\n): ComponentEpochColumns {\n const added = new Float64Array(capacity);\n const changed = new Float64Array(capacity);\n added.set(columns.added);\n changed.set(columns.changed);\n const blocks = new Float64Array(Math.ceil(capacity / PROJECTION_BLOCK_SIZE));\n blocks.set(columns.blocks);\n return { added, changed, blocks };\n}\n\nexport function copyComponentEpoch(\n source: ComponentEpochColumns,\n sourceRow: number,\n target: ComponentEpochColumns,\n targetRow: number,\n): void {\n target.added[targetRow] = source.added[sourceRow] ?? 0;\n target.changed[targetRow] = source.changed[sourceRow] ?? 0;\n const block = Math.floor(targetRow / PROJECTION_BLOCK_SIZE);\n target.blocks[block] = Math.max(target.blocks[block] ?? 0, target.changed[targetRow] ?? 0);\n}\n\nexport interface SparseTagSet {\n readonly component: Component;\n sparse: Int32Array;\n dense: Uint32Array;\n added: Float64Array;\n changed: Float64Array;\n size: number;\n}\n\nexport function createSparseTagSet(component: Component): SparseTagSet {\n const sparse = new Int32Array(INITIAL_SPARSE_CAPACITY);\n sparse.fill(-1);\n return {\n component,\n sparse,\n dense: new Uint32Array(INITIAL_SPARSE_CAPACITY),\n added: new Float64Array(INITIAL_SPARSE_CAPACITY),\n changed: new Float64Array(INITIAL_SPARSE_CAPACITY),\n size: 0,\n };\n}\n\nexport function sparseTagIndex(set: SparseTagSet, entity: EntityHandle): number {\n const denseIndex = set.sparse[entityIndex(entity)] ?? -1;\n return denseIndex >= 0 && set.dense[denseIndex] === (entity as number) ? denseIndex : -1;\n}\n\nexport function sparseTagHas(set: SparseTagSet, entity: EntityHandle): boolean {\n return sparseTagIndex(set, entity) >= 0;\n}\n\nexport function insertSparseTag(set: SparseTagSet, entity: EntityHandle, epoch: number): number {\n const present = sparseTagIndex(set, entity);\n if (present >= 0) {\n set.changed[present] = epoch;\n return present;\n }\n growSparseSlots(set, entityIndex(entity) + 1);\n if (set.size === set.dense.length) growSparseDense(set, set.size + 1);\n const denseIndex = set.size;\n set.dense[denseIndex] = entity as number;\n set.added[denseIndex] = epoch;\n set.changed[denseIndex] = epoch;\n set.sparse[entityIndex(entity)] = denseIndex;\n set.size += 1;\n return denseIndex;\n}\n\nexport function removeSparseTag(set: SparseTagSet, entity: EntityHandle): boolean {\n const denseIndex = sparseTagIndex(set, entity);\n if (denseIndex < 0) return false;\n const lastIndex = set.size - 1;\n set.sparse[entityIndex(entity)] = -1;\n if (denseIndex !== lastIndex) {\n const movedEntity = set.dense[lastIndex] as EntityHandle;\n set.dense[denseIndex] = movedEntity as number;\n set.added[denseIndex] = set.added[lastIndex] ?? 0;\n set.changed[denseIndex] = set.changed[lastIndex] ?? 0;\n set.sparse[entityIndex(movedEntity)] = denseIndex;\n }\n set.size = lastIndex;\n return true;\n}\n\nfunction growSparseSlots(set: SparseTagSet, targetCapacity: number): void {\n if (targetCapacity <= set.sparse.length) return;\n let capacity = set.sparse.length;\n while (capacity < targetCapacity) capacity *= 2;\n const sparse = new Int32Array(capacity);\n sparse.fill(-1);\n sparse.set(set.sparse);\n set.sparse = sparse;\n}\n\nfunction growSparseDense(set: SparseTagSet, targetCapacity: number): void {\n let capacity = set.dense.length;\n while (capacity < targetCapacity) capacity *= 2;\n const dense = new Uint32Array(capacity);\n dense.set(set.dense);\n set.dense = dense;\n const added = new Float64Array(capacity);\n added.set(set.added);\n set.added = added;\n const changed = new Float64Array(capacity);\n changed.set(set.changed);\n set.changed = changed;\n}\n\nexport interface ChangeTicks {\n added: number;\n changed: number;\n}\n\nexport const NEVER_CHANGED_TICK = -1;\n\nexport function createChangeTicks(tick: number): ChangeTicks {\n return { added: tick, changed: tick };\n}\n\ninterface EntityLocation {\n readonly archetypeId: number;\n readonly archetypeRow: number;\n}\n\nexport function readComponentChange(\n graph: ArchetypeGraph,\n location: EntityLocation,\n entity: EntityHandle,\n componentId: number,\n): ChangeTicks | undefined {\n const sparseSet = graph.sparseTags.get(componentId);\n if (sparseSet !== undefined) {\n const denseIndex = sparseTagIndex(sparseSet, entity);\n if (denseIndex < 0) return undefined;\n return {\n added: sparseSet.added[denseIndex] ?? 0,\n changed: sparseSet.changed[denseIndex] ?? 0,\n };\n }\n const archetype = graph.archetypes[location.archetypeId];\n if (archetype === undefined) return undefined;\n const epochs = graph.tables[archetype.tableId]?.storage.get(componentId)?.epochs;\n if (epochs === undefined) return undefined;\n const tableRow = archetype.rows[location.archetypeRow] ?? -1;\n return {\n added: epochs.added[tableRow] ?? 0,\n changed: epochs.changed[tableRow] ?? 0,\n };\n}\n\nexport function markComponentsAdded(\n graph: ArchetypeGraph,\n location: EntityLocation,\n entity: EntityHandle,\n componentIds: readonly number[],\n epoch: number,\n): void {\n const archetype = graph.archetypes[location.archetypeId];\n const table = archetype === undefined ? undefined : graph.tables[archetype.tableId];\n const tableRow = archetype?.rows[location.archetypeRow] ?? -1;\n for (const componentId of componentIds) {\n const component = archetype?.components.find(\n (candidate) => componentOwner.componentId(candidate) === componentId,\n );\n if (component?.storage === 'sparse') {\n insertSparseTag(getOrCreateSparseTagSet(graph, component), entity, epoch);\n continue;\n }\n const epochs = table?.storage.get(componentId)?.epochs;\n if (epochs === undefined) continue;\n epochs.added[tableRow] = epoch;\n publishComponentRange(epochs, tableRow, 1, epoch);\n }\n}\n\nexport function markComponentChanged(\n graph: ArchetypeGraph,\n location: EntityLocation,\n entity: EntityHandle,\n componentId: number,\n epoch: () => number,\n): void {\n const sparseSet = graph.sparseTags.get(componentId);\n if (sparseSet !== undefined) {\n const denseIndex = sparseTagIndex(sparseSet, entity);\n if (denseIndex >= 0) sparseSet.changed[denseIndex] = epoch();\n return;\n }\n const archetype = graph.archetypes[location.archetypeId];\n if (archetype === undefined) return;\n const epochs = graph.tables[archetype.tableId]?.storage.get(componentId)?.epochs;\n if (epochs === undefined) return;\n const tableRow = archetype.rows[location.archetypeRow] ?? -1;\n publishComponentRange(epochs, tableRow, 1, epoch());\n}\n\n/** Row evidence and its conservative block summary share the World epoch. */\nexport function publishComponentRange(\n columns: ComponentEpochColumns,\n start: number,\n count: number,\n epoch: number,\n): void {\n if (count === 0) return;\n if (count === 1) {\n columns.changed[start] = epoch;\n columns.blocks[Math.floor(start / PROJECTION_BLOCK_SIZE)] = epoch;\n return;\n }\n columns.changed.fill(epoch, start, start + count);\n columns.blocks.fill(\n epoch,\n Math.floor(start / PROJECTION_BLOCK_SIZE),\n Math.ceil((start + count) / PROJECTION_BLOCK_SIZE),\n );\n}\n","import type { Component } from '../component';\nimport { componentId } from '../component';\nimport { Entity } from '../entity';\nimport { type EntityHandle, encodeEntity, entityIndex } from '../entity-handle';\nimport { WorldPoisonedError } from '../errors';\nimport { PROJECTION_BLOCK_SIZE } from '../storage/change-detection';\nimport type { Table } from '../storage/table';\nimport type { World } from '../world';\nimport { worldInternal } from '../world-internal';\n\ninterface BlockBaseline {\n readonly ids: Uint32Array;\n readonly membership: number;\n}\n\nexport class StateProjectionExpiredError extends Error {\n readonly code = 'state-projection-expired' as const;\n readonly expected = 'an unmodified source and the latest valid projection candidate';\n readonly hint = 'Read and apply the current state again before accepting the candidate.';\n\n constructor() {\n super('State projection candidate expired; read and apply the current state again.');\n this.name = 'StateProjectionExpiredError';\n }\n}\n\nexport interface StateProjectionBatch {\n /** Source indices, deduplicated across migration and generation replacement. */\n readonly indices: readonly number[];\n readonly epoch: number;\n readonly scannedRows: number;\n readonly checkedBlocks: number;\n readonly membershipChanged: boolean;\n readonly changedComponents: readonly Component[];\n /** Commit only after the owning consumer has successfully applied its candidate. */\n validate(): void;\n accept(): void;\n}\n\nexport interface StateProjection {\n /** Whether the accepted source still matches the live World, without creating a candidate. */\n isCurrent(): boolean;\n read(): StateProjectionBatch;\n /** Resolve the final live generation directly through the World record. */\n entity(index: number): EntityHandle | undefined;\n changed(entity: EntityHandle, component: Component): boolean;\n invalidate(): void;\n}\n\n/**\n * Current-state candidate discovery. Blocks remember accepted identities, never\n * structural operations. Reads are synchronous and accepting a candidate does\n * not consume evidence belonging to another projection.\n */\nexport function createStateProjection(\n world: World,\n components: readonly Component[],\n candidates: readonly Component[] = components,\n): StateProjection {\n const owner = world[worldInternal];\n const graph = owner.getGraph();\n const ids = components.map(componentId);\n const candidateIds = candidates.map(componentId);\n const sparseCandidates = candidates.some((component) => component.storage === 'sparse');\n const baseline = new Map<Table, Map<number, BlockBaseline>>();\n let acceptedEpoch = -1;\n let acceptedStructure = -1;\n let invalid = true;\n let readToken = 0;\n let stamp = new Uint32Array(64);\n let serial = 0;\n const work: number[] = [];\n\n function enqueue(index: number): void {\n if (index >= stamp.length) {\n let size = stamp.length;\n while (size <= index) size *= 2;\n const next = new Uint32Array(size);\n next.set(stamp);\n stamp = next;\n }\n if (stamp[index] === serial) return;\n stamp[index] = serial;\n work.push(index);\n }\n\n return {\n isCurrent() {\n return (\n world.execution.health !== 'poisoned' &&\n !invalid &&\n acceptedEpoch === owner.getMutationEpoch() &&\n acceptedStructure === owner.getStructureEpoch()\n );\n },\n entity(index) {\n const record = owner.getRecords()[index];\n if (record === undefined || record.archetypeId < 0) return undefined;\n return encodeEntity(index, record.generation);\n },\n changed(entity, component) {\n const id = componentId(component);\n if ((owner.getComponentMutationEpochs()[id] ?? 0) <= acceptedEpoch) return false;\n return (owner.getComponentChange(entity, id)?.changed ?? -1) > acceptedEpoch;\n },\n invalidate() {\n readToken++;\n invalid = true;\n },\n read() {\n if (world.execution.health === 'poisoned')\n throw new WorldPoisonedError(world.identity, world.execution.fault);\n const token = ++readToken;\n const epoch = owner.getMutationEpoch();\n const structure = owner.getStructureEpoch();\n const changedComponents = components.filter(\n (component) =>\n (owner.getComponentMutationEpochs()[componentId(component)] ?? 0) > acceptedEpoch,\n );\n const membershipChanged = invalid || structure !== acceptedStructure;\n const changedRoots =\n invalid ||\n structure !== acceptedStructure ||\n ids.some((id) => (owner.getComponentMutationEpochs()[id] ?? 0) > acceptedEpoch);\n work.length = 0;\n serial = (serial + 1) >>> 0;\n if (serial === 0) {\n stamp.fill(0);\n serial = 1;\n }\n let scannedRows = 0;\n let checkedBlocks = 0;\n const updates: { table: Table; block: number; value: BlockBaseline | undefined }[] = [];\n const visited = new Set<Table>();\n if (changedRoots) {\n const tables = new Set<Table>();\n if (sparseCandidates) {\n for (const table of graph.activeTables) tables.add(table);\n } else {\n for (const id of candidateIds)\n for (const table of graph.activeTablesByComponent.get(id) ?? []) tables.add(table);\n }\n for (const table of tables) {\n visited.add(table);\n const prior = baseline.get(table);\n const entities = table.storage.get(componentId(Entity))?.fields.get('self')?.view;\n if (entities === undefined) continue;\n const columns = ids.flatMap((id) => {\n const epochs = table.storage.get(id)?.epochs;\n return epochs === undefined ? [] : [epochs];\n });\n const blocks = Math.ceil(table.size / PROJECTION_BLOCK_SIZE);\n for (let block = 0; block < blocks; block++) {\n checkedBlocks++;\n const previous = prior?.get(block);\n const membership = table.membership[block] ?? 0;\n const start = block * PROJECTION_BLOCK_SIZE;\n const end = Math.min(table.size, start + PROJECTION_BLOCK_SIZE);\n if (invalid || previous === undefined || previous.membership !== membership) {\n if (previous !== undefined) {\n for (const index of previous.ids) enqueue(index);\n scannedRows += previous.ids.length;\n }\n const current = new Uint32Array(end - start);\n for (let row = start; row < end; row++) {\n const index = entityIndex(entities[row] as EntityHandle);\n current[row - start] = index;\n enqueue(index);\n }\n scannedRows += end - start;\n updates.push({ table, block, value: { ids: current, membership } });\n } else {\n let changedBlock = false;\n for (const column of columns) {\n if ((column.blocks[block] ?? 0) <= acceptedEpoch) continue;\n changedBlock = true;\n for (let row = start; row < end; row++) {\n if ((column.changed[row] ?? 0) > acceptedEpoch)\n enqueue(entityIndex(entities[row] as EntityHandle));\n }\n }\n if (!changedBlock) continue;\n scannedRows += end - start;\n }\n }\n if (prior !== undefined) {\n for (const [block, previous] of prior) {\n if (block < blocks) continue;\n checkedBlocks++;\n for (const index of previous.ids) enqueue(index);\n scannedRows += previous.ids.length;\n updates.push({ table, block, value: undefined });\n }\n }\n }\n for (const [table, prior] of baseline) {\n if (visited.has(table)) continue;\n for (const [block, previous] of prior) {\n checkedBlocks++;\n for (const index of previous.ids) enqueue(index);\n scannedRows += previous.ids.length;\n updates.push({ table, block, value: undefined });\n }\n }\n }\n let accepted = false;\n const validate = (): void => {\n if (world.execution.health === 'poisoned')\n throw new WorldPoisonedError(world.identity, world.execution.fault);\n if (\n token !== readToken ||\n epoch !== owner.getMutationEpoch() ||\n structure !== owner.getStructureEpoch()\n ) {\n throw new StateProjectionExpiredError();\n }\n };\n return {\n indices: work,\n epoch,\n scannedRows,\n checkedBlocks,\n membershipChanged,\n changedComponents,\n validate,\n accept() {\n if (accepted) return;\n validate();\n for (const update of updates) {\n let blocks = baseline.get(update.table);\n if (update.value === undefined) {\n blocks?.delete(update.block);\n if (blocks?.size === 0) baseline.delete(update.table);\n } else {\n if (blocks === undefined) {\n blocks = new Map();\n baseline.set(update.table, blocks);\n }\n blocks.set(update.block, update.value);\n }\n }\n acceptedEpoch = epoch;\n acceptedStructure = structure;\n invalid = false;\n accepted = true;\n },\n };\n },\n };\n}\n","import type { Result } from '@forgeax/engine-types';\nimport type { Component } from '../component';\nimport { componentId } from '../component';\nimport type { EntityHandle } from '../entity-handle';\nimport type { EcsError, World } from '../world';\nimport { worldInternal } from '../world-internal';\n\n// Owner packages that need ECS validation/default semantics consume these\n// helpers through the explicit projection surface. They are intentionally not\n// part of the token-first root barrel.\nexport { fillComponentDefaults } from '../component-default-fallback';\nexport {\n ComponentNotDefinedError,\n InstanceTransformsStrideMismatchError,\n ManagedBufferOutOfBoundsError,\n ResourceInvalidValueError,\n SpawnLightInvalidBoundsError,\n SpriteAnimationInvalidError,\n SpriteInstancesCountMismatchError,\n SpriteInstancesMutuallyExclusiveWithInstancesError,\n SpriteInstancesRequiresSpriteShaderError,\n StaleEntityError,\n} from '../errors';\n\nexport interface RenderProjectionComponentRequest {\n readonly component: Component;\n readonly fields: readonly string[];\n}\n\nexport interface RenderProjectionRequest {\n readonly components: readonly RenderProjectionComponentRequest[];\n}\n\nexport interface RenderProjectionSpan {\n readonly length: number;\n readonly fields: Readonly<Record<string, ArrayLike<number>>>;\n}\n\nexport interface RenderProjectionSpans {\n readonly generation: number;\n readonly spans: readonly RenderProjectionSpan[];\n}\n\nexport interface RenderChangeBatch {\n readonly version: RenderReadVersion;\n readonly world: RenderWorldChanges;\n}\n\nexport interface RenderWorldChanges {\n readonly fromEpoch: number;\n readonly toEpoch: number;\n readonly changedComponentIds: readonly number[];\n}\n\nexport interface RenderReadLease {\n readonly worldIdentity: string;\n readonly generation: number;\n readChanges(version: RenderReadVersion): RenderChangeBatch;\n querySpans(request: RenderProjectionRequest): RenderProjectionSpans;\n captureVersion(): RenderReadVersion;\n dispose(): void;\n}\n\n/**\n * Read one array field through the render projection boundary without\n * materialising the component object. Render extraction uses this for hot\n * transform/instance columns; the World internals remain owned by ECS.\n */\nexport function readRenderArrayView(\n world: World,\n entity: EntityHandle,\n component: Component,\n fieldName: string,\n): ArrayLike<number> | undefined {\n return world[worldInternal].getArrayView(entity, component, fieldName) as\n | ArrayLike<number>\n | undefined;\n}\n\nexport interface RenderReadVersion {\n readonly mutationEpoch: number;\n readonly structureEpoch: number;\n}\n\nfunction readProjectionSpans(\n world: World,\n generation: number,\n request: RenderProjectionRequest,\n): RenderProjectionSpans {\n const queryResult = world.query({ read: request.components.map((entry) => entry.component) });\n if (!queryResult.ok) throw new Error(queryResult.error.message);\n const spansResult = queryResult.value.spans();\n if (!spansResult.ok) throw new Error(spansResult.error.message);\n const spans: RenderProjectionSpan[] = [];\n for (const span of spansResult.value) {\n const fields: Record<string, ArrayLike<number>> = {};\n for (const entry of request.components) {\n const shape = span.get(entry.component) as unknown as Record<string, ArrayLike<number>>;\n for (const fieldName of entry.fields) {\n const field = shape[fieldName];\n if (field === undefined) {\n throw new Error(\n `Render projection field '${entry.component.name}.${fieldName}' is unavailable.`,\n );\n }\n fields[`${entry.component.name}.${fieldName}`] = field;\n if (request.components.length === 1) fields[fieldName] = field;\n }\n }\n spans.push({ length: span.length, fields: Object.freeze(fields) });\n }\n return {\n generation,\n spans: Object.freeze(spans),\n };\n}\n\n/** Create the render-owned lease from the ECS projection boundary. */\nexport function createRenderReadLease(world: World, token: object = {}): RenderReadLease {\n void token;\n let disposed = false;\n\n const assertLive = (): void => {\n if (disposed) throw new Error('RenderReadLease is disposed.');\n };\n\n const captureVersion = (): RenderReadVersion => {\n return {\n mutationEpoch: world[worldInternal].getMutationEpoch(),\n structureEpoch: world[worldInternal].getStructureEpoch(),\n };\n };\n\n return {\n worldIdentity: world.identity,\n get generation(): number {\n return Math.max(1, world[worldInternal].getStructureEpoch());\n },\n captureVersion(): RenderReadVersion {\n assertLive();\n return captureVersion();\n },\n readChanges(start: RenderReadVersion): RenderChangeBatch {\n assertLive();\n const toEpoch = world[worldInternal].getMutationEpoch() as number;\n const componentEpochs = world[\n worldInternal\n ].getComponentMutationEpochs() as readonly number[];\n const changedComponentIds: number[] = [];\n for (let componentId = 0; componentId < componentEpochs.length; componentId += 1) {\n const epoch = componentEpochs[componentId] ?? 0;\n if (epoch > start.mutationEpoch && epoch <= toEpoch) changedComponentIds.push(componentId);\n }\n const worldRead: RenderWorldChanges = {\n fromEpoch: start.mutationEpoch,\n toEpoch,\n changedComponentIds,\n };\n const version = captureVersion();\n return { version, world: worldRead };\n },\n querySpans(request: RenderProjectionRequest): RenderProjectionSpans {\n assertLive();\n return readProjectionSpans(\n world,\n Math.max(1, world[worldInternal].getStructureEpoch()),\n request,\n );\n },\n dispose(): void {\n disposed = true;\n },\n };\n}\n\n/**\n * Publish one owner-derived component value through the component's ordinary\n * version. Numeric consumers observe the same row epoch regardless of which\n * owner computed the value; there is no parallel derived-change vocabulary.\n */\nexport function setDerivedComponent(\n world: World,\n entity: EntityHandle,\n component: Component,\n value: Record<string, unknown>,\n): Result<void, EcsError> {\n const result = world[worldInternal].setQueryRow(entity, component, value);\n if (!result.ok) return result;\n world[worldInternal].markComponentChanged(entity, componentId(component));\n return result;\n}\n\n/** Route an owner-domain error through the World without exposing raw internals. */\nexport function routeWorldError(\n world: World,\n error: unknown,\n context?: { readonly systemName: string },\n): void {\n world[worldInternal].routeError(error, context);\n}\n\nexport {\n createStateProjection,\n type StateProjection,\n type StateProjectionBatch,\n StateProjectionExpiredError,\n} from './state-projection';\n"]}