@forgeax/engine-ecs 0.1.33 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -18
- package/dist/__tests__/query-idle.unit.test.d.ts +2 -0
- package/dist/__tests__/query-idle.unit.test.d.ts.map +1 -0
- package/dist/__tests__/set-allocation.unit.test.d.ts +2 -0
- package/dist/__tests__/set-allocation.unit.test.d.ts.map +1 -0
- package/dist/__tests__/state-projection.unit.test.d.ts +2 -0
- package/dist/__tests__/state-projection.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +579 -623
- package/dist/index.mjs.map +1 -1
- package/dist/projection/index.d.ts +2 -18
- package/dist/projection/index.d.ts.map +1 -1
- package/dist/projection/index.mjs +433 -23
- package/dist/projection/index.mjs.map +1 -1
- package/dist/projection/state-projection.d.ts +37 -0
- package/dist/projection/state-projection.d.ts.map +1 -0
- package/dist/query/query.d.ts.map +1 -1
- package/dist/shared-ref-store.d.ts +0 -24
- package/dist/shared-ref-store.d.ts.map +1 -1
- package/dist/shared.mjs.map +1 -1
- package/dist/storage/archetype-graph.d.ts +2 -0
- package/dist/storage/archetype-graph.d.ts.map +1 -1
- package/dist/storage/change-detection.d.ts +4 -0
- package/dist/storage/change-detection.d.ts.map +1 -1
- package/dist/storage/table.d.ts +6 -3
- package/dist/storage/table.d.ts.map +1 -1
- package/dist/world-internal.d.ts +0 -2
- package/dist/world-internal.d.ts.map +1 -1
- package/dist/world.d.ts +0 -2
- package/dist/world.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/component-version-surface.test.ts +1 -7
- package/src/__tests__/derived-range-writer.contract.test.ts +6 -0
- package/src/__tests__/ecs-core-reduction.characterization.test.ts +2 -3
- package/src/__tests__/execution-conflict-boundary.unit.test.ts +4 -0
- package/src/__tests__/externalization-render-read-lease.unit.test.ts +4 -10
- package/src/__tests__/query-idle.unit.test.ts +19 -0
- package/src/__tests__/set-allocation.unit.test.ts +30 -0
- package/src/__tests__/shared-ref-lifetime.unit.test.ts +1 -2
- package/src/__tests__/shared-ref-store.unit.test.ts +2 -34
- package/src/__tests__/state-projection.unit.test.ts +159 -0
- package/src/__tests__/world-health.contract.test.ts +0 -23
- package/src/index.ts +1 -1
- package/src/projection/index.ts +9 -39
- package/src/projection/state-projection.ts +250 -0
- package/src/query/query.ts +23 -16
- package/src/shared-ref-store.ts +0 -70
- package/src/storage/archetype-graph.ts +15 -1
- package/src/storage/change-detection.ts +36 -4
- package/src/storage/table.ts +20 -1
- package/src/world-internal.ts +0 -2
- package/src/world.ts +35 -38
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts +0 -2
- package/dist/__tests__/structural-evidence.contract.test-d.d.ts.map +0 -1
- package/dist/__tests__/structural-evidence.contract.test.d.ts +0 -2
- package/dist/__tests__/structural-evidence.contract.test.d.ts.map +0 -1
- package/dist/storage/structural-evidence.d.ts +0 -30
- package/dist/storage/structural-evidence.d.ts.map +0 -1
- package/src/__tests__/structural-evidence.contract.test-d.ts +0 -6
- package/src/__tests__/structural-evidence.contract.test.ts +0 -49
- package/src/storage/structural-evidence.ts +0 -64
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Component } from '../component';
|
|
2
|
+
import { type EntityHandle } from '../entity-handle';
|
|
3
|
+
import type { World } from '../world';
|
|
4
|
+
export declare class StateProjectionExpiredError extends Error {
|
|
5
|
+
readonly code: "state-projection-expired";
|
|
6
|
+
readonly expected = "an unmodified source and the latest valid projection candidate";
|
|
7
|
+
readonly hint = "Read and apply the current state again before accepting the candidate.";
|
|
8
|
+
constructor();
|
|
9
|
+
}
|
|
10
|
+
export interface StateProjectionBatch {
|
|
11
|
+
/** Source indices, deduplicated across migration and generation replacement. */
|
|
12
|
+
readonly indices: readonly number[];
|
|
13
|
+
readonly epoch: number;
|
|
14
|
+
readonly scannedRows: number;
|
|
15
|
+
readonly checkedBlocks: number;
|
|
16
|
+
readonly membershipChanged: boolean;
|
|
17
|
+
readonly changedComponents: readonly Component[];
|
|
18
|
+
/** Commit only after the owning consumer has successfully applied its candidate. */
|
|
19
|
+
validate(): void;
|
|
20
|
+
accept(): void;
|
|
21
|
+
}
|
|
22
|
+
export interface StateProjection {
|
|
23
|
+
/** Whether the accepted source still matches the live World, without creating a candidate. */
|
|
24
|
+
isCurrent(): boolean;
|
|
25
|
+
read(): StateProjectionBatch;
|
|
26
|
+
/** Resolve the final live generation directly through the World record. */
|
|
27
|
+
entity(index: number): EntityHandle | undefined;
|
|
28
|
+
changed(entity: EntityHandle, component: Component): boolean;
|
|
29
|
+
invalidate(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Current-state candidate discovery. Blocks remember accepted identities, never
|
|
33
|
+
* structural operations. Reads are synchronous and accepting a candidate does
|
|
34
|
+
* not consume evidence belonging to another projection.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createStateProjection(world: World, components: readonly Component[], candidates?: readonly Component[]): StateProjection;
|
|
37
|
+
//# sourceMappingURL=state-projection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-projection.d.ts","sourceRoot":"","sources":["../../src/projection/state-projection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAG9C,OAAO,EAAE,KAAK,YAAY,EAA6B,MAAM,kBAAkB,CAAC;AAIhF,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAQtC,qBAAa,2BAA4B,SAAQ,KAAK;IACpD,QAAQ,CAAC,IAAI,EAAG,0BAA0B,CAAU;IACpD,QAAQ,CAAC,QAAQ,oEAAoE;IACrF,QAAQ,CAAC,IAAI,4EAA4E;;CAM1F;AAED,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,SAAS,EAAE,CAAC;IACjD,oFAAoF;IACpF,QAAQ,IAAI,IAAI,CAAC;IACjB,MAAM,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,SAAS,IAAI,OAAO,CAAC;IACrB,IAAI,IAAI,oBAAoB,CAAC;IAC7B,2EAA2E;IAC3E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAAC;IAChD,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC;IAC7D,UAAU,IAAI,IAAI,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,KAAK,EACZ,UAAU,EAAE,SAAS,SAAS,EAAE,EAChC,UAAU,GAAE,SAAS,SAAS,EAAe,GAC5C,eAAe,CA+LjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/query/query.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/query/query.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,12 @@
|
|
|
1
1
|
import type { Handle } from '@forgeax/engine-types';
|
|
2
2
|
import { type Result } from '@forgeax/engine-types';
|
|
3
3
|
import { BuiltinSlotNotOwnedError, SharedRefDoubleReleaseError, SharedRefReleasedError, SharedRefStaleError } from './errors.js';
|
|
4
|
-
export interface SharedRefMutation {
|
|
5
|
-
readonly epoch: number;
|
|
6
|
-
readonly handle: number;
|
|
7
|
-
}
|
|
8
4
|
export interface SharedRefReleaseEvidence {
|
|
9
5
|
readonly payload: unknown;
|
|
10
6
|
readonly refcount: 0;
|
|
11
7
|
readonly generation: number;
|
|
12
8
|
readonly evidence: 'released';
|
|
13
9
|
}
|
|
14
|
-
/** Diagnostic history identifies releases without retaining external payloads. */
|
|
15
|
-
export type SharedRefReleaseRecord = Omit<SharedRefReleaseEvidence, 'payload'> & {
|
|
16
|
-
readonly handle: number;
|
|
17
|
-
};
|
|
18
|
-
export interface SharedRefMutationRead {
|
|
19
|
-
readonly cursor: number;
|
|
20
|
-
readonly records: readonly SharedRefMutation[];
|
|
21
|
-
}
|
|
22
10
|
/**
|
|
23
11
|
* Reference-counted store for ECS-aware `Handle<T, 'shared'>` lifecycles.
|
|
24
12
|
*
|
|
@@ -35,8 +23,6 @@ export interface SharedRefMutationRead {
|
|
|
35
23
|
* - alloc(target, payload) -> Handle<T, 'shared'> (rc=1)
|
|
36
24
|
* - intern(target, payload) -> stable producer handle per target + object identity
|
|
37
25
|
* - resolve(handle) -> Result<T, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
|
|
38
|
-
* - markChanged(handle) -> Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
|
|
39
|
-
* - getMutationEpoch() -> monotonic payload-mutation cursor
|
|
40
26
|
* - retain(handle) -> Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>
|
|
41
27
|
* - release(handle) -> Result<release evidence | undefined, ...>
|
|
42
28
|
* - refcount(handle) -> number (0 == released; debug + tests)
|
|
@@ -49,10 +35,7 @@ export declare class SharedRefStore {
|
|
|
49
35
|
private readonly internedByTarget;
|
|
50
36
|
private readonly internedKeys;
|
|
51
37
|
private nextSlot;
|
|
52
|
-
private mutationEpoch;
|
|
53
38
|
/** Latest published mutation epoch per live handle; not an event journal. */
|
|
54
|
-
private readonly mutationEpochs;
|
|
55
|
-
private readonly releaseJournal;
|
|
56
39
|
/**
|
|
57
40
|
* Generation table indexed by slot (D-6). Each entry tracks the current
|
|
58
41
|
* generation for the slot — written to during alloc (welded into the
|
|
@@ -103,11 +86,6 @@ export declare class SharedRefStore {
|
|
|
103
86
|
* projections of shared payload data compare the monotonic epoch and
|
|
104
87
|
* explicitly refresh instead of rescanning every payload each frame.
|
|
105
88
|
*/
|
|
106
|
-
markChanged<Target extends string>(handle: Handle<Target, 'shared'>): Result<void, SharedRefReleasedError | SharedRefStaleError | BuiltinSlotNotOwnedError>;
|
|
107
|
-
/** Current upper bound for explicitly published payload mutations. */
|
|
108
|
-
getMutationEpoch(): number;
|
|
109
|
-
/** Read each live handle whose latest published mutation is after `cursor`. */
|
|
110
|
-
readChangesSince(cursor: number): SharedRefMutationRead;
|
|
111
89
|
/**
|
|
112
90
|
* Increment the refcount of a live shared handle. Returns
|
|
113
91
|
* `err(shared-ref-released)` when the handle is not live - retain MUST
|
|
@@ -129,8 +107,6 @@ export declare class SharedRefStore {
|
|
|
129
107
|
* chain.
|
|
130
108
|
*/
|
|
131
109
|
release<Target extends string>(handle: Handle<Target, 'shared'>): Result<SharedRefReleaseEvidence | undefined, SharedRefDoubleReleaseError | SharedRefStaleError | BuiltinSlotNotOwnedError>;
|
|
132
|
-
/** Read bounded release metadata; payload ownership remains with the caller. */
|
|
133
|
-
readReleaseEvidence(): readonly SharedRefReleaseRecord[];
|
|
134
110
|
/**
|
|
135
111
|
* Return the current refcount for `handle`. Returns 0 for a released
|
|
136
112
|
* (or never-allocated) slot. Primarily a debug + tests entry point;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared-ref-store.d.ts","sourceRoot":"","sources":["../src/shared-ref-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shared-ref-store.d.ts","sourceRoot":"","sources":["../src/shared-ref-store.ts"],"names":[],"mappings":"AA+DA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EASL,KAAK,MAAM,EAGZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,wBAAwB,EACxB,2BAA2B,EAE3B,sBAAsB,EACtB,mBAAmB,EACpB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC;CAC/B;AAKD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgB;IAC1C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA8C;IAC/E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAGzB;IACJ,OAAO,CAAC,QAAQ,CAAgB;IAChC,6EAA6E;IAE7E;;;;;;OAMG;IAEH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgB;IAE7C;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,MAAM,SAAS,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IAmB/F;;;;;;;;;OASG;IACH,MAAM,CAAC,MAAM,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,EAC5C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,GACT,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC;IAmB3B;;;;;;;;;OASG;IACH,OAAO,CAAC,MAAM,SAAS,MAAM,EAAE,CAAC,GAAG,OAAO,EACxC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC/B,MAAM,CAAC,CAAC,EAAE,sBAAsB,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;IAoBrF;;;;OAIG;IAEH;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,SAAS,MAAM,EAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC/B,MAAM,CAAC,IAAI,EAAE,sBAAsB,GAAG,mBAAmB,GAAG,wBAAwB,CAAC;IAkBxF;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,MAAM,SAAS,MAAM,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAC/B,MAAM,CACP,wBAAwB,GAAG,SAAS,EACpC,2BAA2B,GAAG,mBAAmB,GAAG,wBAAwB,CAC7E;IAmDD;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,SAAS,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,MAAM;IAKzE,+EAA+E;IAC/E,UAAU,IAAI,MAAM;CAGrB"}
|
package/dist/shared.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/component.ts","../src/world-internal.ts","../src/execution/shared-kernel.ts"],"names":[],"mappings":"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","file":"shared.mjs","sourcesContent":["// @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","/**\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","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"]}
|
|
1
|
+
{"version":3,"sources":["../src/component.ts","../src/world-internal.ts","../src/execution/shared-kernel.ts"],"names":[],"mappings":"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","file":"shared.mjs","sourcesContent":["// @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","/**\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"]}
|
|
@@ -21,6 +21,8 @@ export interface ArchetypeGraph {
|
|
|
21
21
|
/** Global generation counter. Incremented on each new archetype. */
|
|
22
22
|
generation: number;
|
|
23
23
|
tables: Table[];
|
|
24
|
+
activeTables: Set<Table>;
|
|
25
|
+
activeTablesByComponent: Map<ComponentId, Set<Table>>;
|
|
24
26
|
tableDedupByKey: Map<string, TableId>;
|
|
25
27
|
tableGeneration: number;
|
|
26
28
|
sparseTags: Map<ComponentId, SparseTagSet>;
|