@forgeax/engine-scene 0.1.2 → 0.1.4

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../types/src/asset-errors.ts","../../types/src/result.ts","../../types/src/asset-evidence.ts","../../types/src/handle.ts","../../types/src/material/asset.ts","../../types/src/material/color-space.ts","../../types/src/material/errors.ts","../../types/src/material/resolve.ts","../../types/src/runtime-scope.ts","../../types/src/derive-paramschema.ts","../../types/src/asset-producer.ts","../../types/src/import.ts","../../types/src/index.ts","../src/assets/scene-decoder.ts","../src/assets/scene-projection.ts","../src/instances/scene-instances.ts","../src/errors.ts","../src/components/children.ts","../src/components/morph-weights.ts","../src/components/name.ts","../src/components/transform.ts","../src/instances/collect-profile.ts","../src/instances/externalization.ts","../src/systems/propagate-transforms.ts","../src/systems/hierarchy-projection.ts","../src/plugin.ts"],"sourcesContent":["/** Closed structured error contracts for Pack v2 and asset evidence. */\n\nexport type AssetLoadErrorCode =\n | 'asset-guid-invalid'\n | 'asset-kind-mismatch'\n | 'asset-not-found'\n | 'asset-not-ready'\n | 'catalog-unavailable'\n | 'catalog-discontinuous'\n | 'asset-fetch-failed'\n | 'asset-integrity-failed'\n | 'asset-package-invalid'\n | 'asset-decoder-missing'\n | 'asset-decode-failed'\n | 'asset-dependency-failed'\n | 'asset-superseded'\n | 'asset-load-cancelled'\n | 'asset-runtime-disposed';\n\nexport const ASSET_LOAD_ERROR_HINTS: Readonly<Record<AssetLoadErrorCode, string>> = {\n 'asset-guid-invalid': 'provide a valid asset GUID and retry the current publication',\n 'asset-kind-mismatch': 'pass the Catalog kind or the matching custom AssetKind token',\n 'asset-not-found': 'inspect the producer Catalog and rebuild the missing publication',\n 'asset-not-ready': 'wait for the current publication or inspect its producer lifecycle',\n 'catalog-unavailable': 'inspect the scope and create a fresh Registry for a new scope',\n 'catalog-discontinuous': 'reconcile the Catalog baseline before loading the current row',\n 'asset-fetch-failed': 'verify the package locator and republish the Pack',\n 'asset-integrity-failed': 'verify the artifact digest and recook the Pack',\n 'asset-package-invalid': 'validate the Pack v2 envelope and recook invalid output',\n 'asset-decoder-missing': 'install the owner decoder lease for this kind',\n 'asset-decode-failed': 'inspect the structured decoder detail and repair the owner output',\n 'asset-dependency-failed': 'repair the dependency publication named in detail and retry',\n 'asset-superseded': 'load the current publication instead of the superseded ticket',\n 'asset-load-cancelled': 'retry with a live AbortSignal when the request is still needed',\n 'asset-runtime-disposed': 'obtain a new Registry from the current realm',\n};\n\ntype AssetLoadErrorBase<C extends AssetLoadErrorCode, D> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: D;\n};\n\nexport type AssetLoadError =\n | AssetLoadErrorBase<'asset-guid-invalid', { readonly guid: string }>\n | AssetLoadErrorBase<\n 'asset-kind-mismatch',\n { readonly guid: string; readonly expectedKind: string; readonly actualKind: string }\n >\n | AssetLoadErrorBase<'asset-not-found', { readonly guid: string }>\n | AssetLoadErrorBase<'asset-not-ready', { readonly guid: string; readonly generation: number }>\n | AssetLoadErrorBase<'catalog-unavailable', { readonly scopeId: string }>\n | AssetLoadErrorBase<\n 'catalog-discontinuous',\n {\n readonly scopeId: string;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n }\n >\n | AssetLoadErrorBase<'asset-fetch-failed', { readonly guid: string; readonly packageUrl: string }>\n | AssetLoadErrorBase<\n 'asset-integrity-failed',\n {\n readonly guid: string;\n readonly artifactKey: string;\n readonly expectedDigest: string;\n readonly actualDigest: string;\n }\n >\n | AssetLoadErrorBase<'asset-package-invalid', { readonly guid: string; readonly reason: string }>\n | AssetLoadErrorBase<'asset-decoder-missing', { readonly kind: string }>\n | AssetLoadErrorBase<'asset-decode-failed', { readonly guid: string; readonly kind: string }>\n | AssetLoadErrorBase<\n 'asset-dependency-failed',\n { readonly guid: string; readonly dependencyGuid: string }\n >\n | AssetLoadErrorBase<'asset-superseded', { readonly guid: string; readonly generation: number }>\n | AssetLoadErrorBase<'asset-load-cancelled', { readonly guid: string }>\n | AssetLoadErrorBase<'asset-runtime-disposed', { readonly scopeId: string }>;\n\nexport type AssetArtifactErrorCode =\n | 'asset-artifact-path-invalid'\n | 'asset-artifact-missing'\n | 'asset-artifact-integrity-mismatch'\n | 'asset-artifact-media-unsupported'\n | 'asset-artifact-codec-unsupported'\n | 'asset-artifact-encoding-unsupported'\n | 'asset-artifact-decode-failed';\n\nexport type AssetArtifactErrorDetail =\n | {\n readonly guid: string;\n readonly artifactKey: string;\n readonly observed: string;\n readonly expected: string;\n }\n | {\n readonly guid: string;\n readonly artifactKey: string;\n readonly observed: string;\n readonly expected: string;\n readonly path: string;\n };\n\nexport type AssetArtifactError =\n | {\n readonly code: 'asset-artifact-path-invalid';\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly artifactKey: string }>;\n }\n | {\n readonly code: 'asset-artifact-missing';\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly path: string }>;\n }\n | {\n readonly code: Exclude<\n AssetArtifactErrorCode,\n 'asset-artifact-path-invalid' | 'asset-artifact-missing'\n >;\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly artifactKey: string }>;\n };\n\nexport type PackV2ErrorCode =\n | 'pack-v2-version-unsupported'\n | 'pack-v2-envelope-invalid'\n | 'pack-v2-duplicate-guid'\n | 'pack-v2-duplicate-artifact-key'\n | 'pack-v2-artifact-descriptor-invalid';\n\nexport type PackV2ErrorDetail =\n | { readonly observed: string; readonly expected: string }\n | { readonly guid: string; readonly paths: readonly string[] }\n | { readonly guid: string; readonly artifactKey: string }\n | { readonly guid: string; readonly artifactKey: string; readonly field: string };\n\nexport interface PackV2Error {\n readonly code: PackV2ErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: PackV2ErrorDetail;\n}\n\nexport type AssetEvidenceErrorDetail =\n | { readonly capability: string; readonly stage: string }\n | { readonly guid: string; readonly observed: string; readonly expected: string };\n\nexport interface AssetEvidenceError {\n readonly code: AssetEvidenceErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AssetEvidenceErrorDetail;\n}\n\nexport const ASSET_EVIDENCE_ERROR_HINTS = {\n 'asset-evidence-capability-missing':\n 'provide the missing evidence capability, then rerun asset lookup or verify',\n 'asset-evidence-source-conflict':\n 'keep one source declaration per GUID and rerun the offline evidence projection',\n 'asset-evidence-locator-conflict':\n 'keep one packageUrl per GUID and rebuild the catalog before verifying the asset',\n 'asset-evidence-receipt-conflict':\n 'keep one producer-owned receipt per GUID and rerun cook before verifying the asset',\n 'asset-evidence-digest-mismatch':\n 'recook the source or restore the package bytes, then rerun artifact verification',\n} satisfies Readonly<Record<string, string>>;\n\nexport type AssetEvidenceErrorCode = keyof typeof ASSET_EVIDENCE_ERROR_HINTS;\n\n/** Ordered author-to-runtime stages used by structured recovery errors. */\nexport type AssetErrorStage =\n | 'author-validation'\n | 'external-declaration'\n | 'import'\n | 'native-cook'\n | 'ddc-validation'\n | 'runtime-parse'\n | 'editor-capability';\n\n/** AI-readable next action; it does not grant a cache or runtime write authority. */\nexport interface AssetStageRecovery {\n readonly action: string;\n readonly command?: string;\n readonly retryable: boolean;\n}\n\nexport type AssetStageErrorDetail =\n | { readonly authoringPath: string; readonly rule: string }\n | { readonly sourceKey: string; readonly sourceIndex?: number }\n | { readonly sourcePath: string; readonly importer?: string }\n | { readonly guid: string; readonly producer: string }\n | { readonly guid: string; readonly observedDigest: string; readonly expectedDigest: string }\n | { readonly guid: string; readonly packageUrl: string }\n | { readonly capability: string; readonly assetKind: string };\n\nexport interface AssetStageErrorBase<S extends AssetErrorStage, C extends string> {\n readonly stage: S;\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AssetStageErrorDetail;\n readonly recovery: AssetStageRecovery;\n}\n\ntype AssetStageErrorWithDetail<\n S extends AssetErrorStage,\n C extends string,\n D extends AssetStageErrorDetail,\n> = Omit<AssetStageErrorBase<S, C>, 'detail'> & { readonly detail: D };\n\nexport type AuthorValidationError = AssetStageErrorWithDetail<\n 'author-validation',\n 'author-validation-failed',\n Extract<AssetStageErrorDetail, { readonly authoringPath: string }>\n>;\nexport type ExternalDeclarationError = AssetStageErrorWithDetail<\n 'external-declaration',\n 'external-declaration-invalid',\n Extract<AssetStageErrorDetail, { readonly sourceKey: string }>\n>;\nexport type ImportStageError = AssetStageErrorWithDetail<\n 'import',\n 'import-failed',\n Extract<AssetStageErrorDetail, { readonly sourcePath: string }>\n>;\nexport type NativeCookError = AssetStageErrorWithDetail<\n 'native-cook',\n 'native-cook-failed',\n Extract<AssetStageErrorDetail, { readonly guid: string; readonly producer: string }>\n>;\nexport type DdcValidationError = AssetStageErrorWithDetail<\n 'ddc-validation',\n 'ddc-validation-failed',\n Extract<AssetStageErrorDetail, { readonly observedDigest: string }>\n>;\nexport type RuntimeParseError = AssetStageErrorWithDetail<\n 'runtime-parse',\n 'runtime-parse-failed',\n Extract<AssetStageErrorDetail, { readonly packageUrl: string }>\n>;\nexport type EditorCapabilityError = AssetStageErrorWithDetail<\n 'editor-capability',\n 'editor-capability-unavailable',\n Extract<AssetStageErrorDetail, { readonly capability: string }>\n>;\n\nexport type AssetStageError =\n | AuthorValidationError\n | ExternalDeclarationError\n | ImportStageError\n | NativeCookError\n | DdcValidationError\n | RuntimeParseError\n | EditorCapabilityError;\n\nexport type AssetStageErrorCode = AssetStageError['code'];\n\nexport const ASSET_STAGE_ERROR_HINTS: Readonly<Record<AssetStageErrorCode, string>> = {\n 'author-validation-failed': 'read the authoring rule and apply the suggested recovery',\n 'external-declaration-invalid': 'repair the sourceKey declaration and retry recovery',\n 'import-failed': 'fix the importer input or registration, then retry recovery',\n 'native-cook-failed': 'fix the native producer and rerun recovery',\n 'ddc-validation-failed': 'repair the cooked artifact and rerun recovery',\n 'runtime-parse-failed': 'repair the package payload and rerun recovery',\n 'editor-capability-unavailable': 'register the capability and retry recovery',\n};\n","// @forgeax/engine-types — Result<T, E> SSOT (tweak-20260612-result-into-types).\n//\n// `Result<T, E>` is the project-wide binary success/failure carrier. It used to\n// live as TWO byte-aligned copies (`packages/rhi/src/errors.ts` +\n// `packages/ecs/src/result.ts`) — that \"byte-for-byte aligned\" prose was a\n// declaration, not a mechanism, and silently drifted. Consolidating here:\n// - One physical source for the discriminated union + `ok`/`err` factories.\n// - rhi / ecs each keep their typed error class (RhiError / EcsError union)\n// and just re-export the Result shape from this module.\n// - Generic parameter is intentionally NOT defaulted — each consumer narrows\n// the error parameter at its own boundary (`Result<T, RhiError>`,\n// `Result<T, EcsError>`).\n//\n// Charter mapping: P5 consistent abstraction (single Result idiom across rhi\n// and ecs); SSOT data layer (one authoritative carrier, derive don't duplicate).\n\n// ────────────────────────────────────────────────────────────────────────────\n// Narrow shape — boolean discriminant `.ok` + `.value` / `.error` branches\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Success branch — plain field access (`.ok === true`, `.value: T`) plus\n * method chain (`.unwrap()` / `.unwrapOr(default)`).\n *\n * On the ok branch `unwrap()` returns `.value`; `unwrapOr(d)` ignores the\n * default and returns `.value` (charter proposition 4 explicit-failure: the\n * throwing path lives only on the err branch).\n */\nexport interface ResultOk<T> {\n readonly ok: true;\n readonly value: T;\n /** Return the wrapped value. Never throws on the ok branch. */\n unwrap(): T;\n /** Return the wrapped value; the default argument is unused on the ok branch. */\n unwrapOr(defaultValue: T): T;\n}\n\n/**\n * Failure branch — plain field access (`.ok === false`, `.error: E`) plus\n * method chain (`.unwrap()` / `.unwrapOr(default)`).\n *\n * On the err branch `unwrap()` throws the underlying `E` (not wrapped in a\n * fresh `Error`) so AI consumers preserve `.code` / `.expected` / `.hint` for\n * programmatic recovery (charter proposition 4 + AGENTS.md \"Errors are\n * structured. Return Result, never throw for expected failures.\" — `.unwrap()`\n * is the explicit Layer 3 ErrorHandler boundary, not a hidden throw).\n *\n * `unwrapOr(d)` returns the default; the original error is silently dropped.\n * Use `if (!r.ok) ...` plus `r.error` if you need to inspect the failure.\n */\nexport interface ResultErr<E> {\n readonly ok: false;\n readonly error: E;\n /** Throw the underlying `error` (preserved without rewrapping). */\n unwrap(): never;\n /** Return the supplied default value (the original error is silently dropped). */\n unwrapOr<T>(defaultValue: T): T;\n}\n\n/**\n * Discriminated union: either `ResultOk<T>` or `ResultErr<E>`.\n *\n * Use `ok(value)` / `err(error)` factories to create instances.\n * Use `if (r.ok) ...` / `if (!r.ok) ...` to narrow.\n *\n * Width-assignment friendly: a `ResultErr<E>` returned by `err(...)` typed as\n * `Result<never, E>` satisfies any `Result<X, E>` after a `if (!r.ok)` narrow,\n * so `return r;` propagates without a cast.\n */\nexport type Result<T, E> = ResultOk<T> | ResultErr<E>;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Prototype objects (shared methods — dimorphic, V8 friendly)\n// ────────────────────────────────────────────────────────────────────────────\n\nconst OK_PROTO = {\n unwrap(this: ResultOk<unknown>): unknown {\n return this.value;\n },\n unwrapOr(this: ResultOk<unknown>, _defaultValue: unknown): unknown {\n return this.value;\n },\n};\n\nconst ERR_PROTO = {\n unwrap(this: ResultErr<unknown>): never {\n // Throw the ORIGINAL error — NOT wrapped in new Error()\n throw this.error;\n },\n unwrapOr<T>(this: ResultErr<unknown>, defaultValue: T): T {\n return defaultValue;\n },\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// Factory functions\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Construct a success branch.\n *\n * Returns the narrow `ResultOk<T>` shape so direct `.value` access works\n * without a `if (r.ok)` narrow at the call site (a `ResultOk<T>` widens to\n * `Result<T, E>` for any `E` by structural assignment).\n */\nexport function ok<T>(value: T): ResultOk<T> {\n const r = Object.create(OK_PROTO) as { ok: true; value: T };\n r.ok = true;\n r.value = value;\n return r as ResultOk<T>;\n}\n\n/**\n * Construct a failure branch.\n *\n * Returns the narrow `ResultErr<E>` shape so direct `.error` access works\n * without a `if (!r.ok)` narrow at the call site. After a `if (!r.ok) return\n * r;` narrow, `ResultErr<E>` widens structurally to any `Result<X, E>` so\n * `return r;` propagates without a cast.\n */\nexport function err<E>(error: E): ResultErr<E> {\n const r = Object.create(ERR_PROTO) as { ok: false; error: E };\n r.ok = false;\n r.error = error;\n return r as ResultErr<E>;\n}\n","import type {\n ArtifactDescriptor,\n ArtifactVerificationStatus,\n ContentEncoding,\n CookFreshness,\n CookOrigin,\n CookReceiptStatus,\n CookStatus,\n Integrity,\n RuntimeEvidenceStatus,\n} from './asset.js';\nimport {\n ASSET_EVIDENCE_ERROR_HINTS,\n type AssetEvidenceError,\n type AssetEvidenceErrorCode,\n} from './asset-errors.js';\nimport { err, ok, type Result } from './result.js';\n\nexport type {\n AssetEvidenceError,\n AssetEvidenceErrorCode,\n AssetEvidenceErrorDetail,\n} from './asset-errors.js';\n\n/** Producer-owned cook attempt; a receipt is evidence only for its own input fingerprint. */\nexport interface CookReceipt {\n readonly guid: string;\n readonly origin: CookOrigin;\n readonly status: CookReceiptStatus;\n readonly inputFingerprint: string;\n readonly outputDigest?: string;\n readonly error?: {\n readonly code: string;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: unknown;\n };\n}\n\nexport type { CookProduct } from './asset.js';\n\n/** Source-side declaration used to compare current input with a producer receipt. */\nexport interface SourceDeclarationEvidence {\n readonly origin: CookOrigin;\n readonly sourcePath?: string;\n readonly inputFingerprint?: string;\n}\n\n/** Verification state for the Pack v2 package envelope, independent of cook freshness. */\nexport interface PackageVerificationEvidence {\n readonly status: ArtifactVerificationStatus;\n readonly digest?: string;\n}\n\n/** Descriptor plus the explicit verification result for one package artifact. */\nexport interface AssetEvidenceArtifact {\n readonly descriptor: ArtifactDescriptor;\n readonly verification: ArtifactVerificationStatus;\n}\n\nexport type AssetEvidenceStatus = 'passed' | 'notChecked' | 'failed' | 'unknown';\n\n/** Derived GUID evidence; use the closed states literally and follow error hints. */\nexport interface AssetEvidence {\n readonly guid: string;\n readonly packageUrl?: string;\n readonly cookReceiptUrl?: string;\n readonly source?: SourceDeclarationEvidence;\n readonly cook: {\n readonly status: CookStatus;\n readonly freshness: CookFreshness;\n readonly receipt?: CookReceipt;\n };\n readonly package?: PackageVerificationEvidence;\n readonly artifacts: Readonly<Record<string, AssetEvidenceArtifact>>;\n readonly runtime: {\n readonly status: RuntimeEvidenceStatus;\n };\n}\n\n/** Catalog navigation only; the URLs locate evidence but do not prove readiness. */\nexport interface AssetEvidenceLocator {\n readonly packageUrl: string;\n readonly cookReceiptUrl?: string;\n}\n\nexport interface AssetEvidencePackageInput {\n readonly guid: string;\n readonly digest?: string;\n readonly artifacts: Readonly<\n Record<\n string,\n {\n readonly descriptor: ArtifactDescriptor;\n readonly verification?: ArtifactVerificationStatus;\n }\n >\n >;\n}\n\n/** Inputs accepted by the pure projector; producers supply facts, not inferred status. */\nexport interface AssetEvidenceInputs {\n readonly guid: string;\n readonly source?: SourceDeclarationEvidence;\n readonly sources?: readonly SourceDeclarationEvidence[];\n readonly locator?: AssetEvidenceLocator;\n readonly locators?: readonly AssetEvidenceLocator[];\n readonly receipt?: CookReceipt;\n readonly receipts?: readonly CookReceipt[];\n readonly packageVerification?: PackageVerificationEvidence;\n readonly package?: AssetEvidencePackageInput;\n readonly artifacts?: Readonly<Record<string, ArtifactDescriptor>>;\n readonly artifactVerification?: Readonly<Record<string, ArtifactVerificationStatus>>;\n readonly runtime?: { readonly status: RuntimeEvidenceStatus };\n}\n\n/** Project the one public producer product into the shared evidence view. */\nexport function projectCookProductEvidence(\n product: import('./asset.js').CookProduct,\n locator?: AssetEvidenceLocator,\n): Result<AssetEvidence, AssetEvidenceError> {\n if (product.receipt.guid !== product.guid) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n product.guid,\n product.receipt.guid,\n product.guid,\n );\n }\n if (product.receipt.outputDigest !== product.digest) {\n return conflict(\n 'asset-evidence-digest-mismatch',\n product.guid,\n product.digest,\n product.receipt.outputDigest ?? 'missing receipt digest',\n );\n }\n return projectAssetEvidence({\n guid: product.guid,\n source: {\n origin: product.receipt.origin,\n inputFingerprint: product.receipt.inputFingerprint,\n },\n ...(locator === undefined ? {} : { locator }),\n receipt: product.receipt,\n package: {\n guid: product.guid,\n digest: product.digest,\n artifacts: Object.fromEntries(\n Object.entries(product.artifacts).map(([key, descriptor]) => [\n key,\n { descriptor, verification: 'passed' as const },\n ]),\n ),\n },\n });\n}\n\nexport { ASSET_EVIDENCE_ERROR_HINTS } from './asset-errors.js';\n\nfunction conflict(\n code: AssetEvidenceErrorCode,\n guid: string,\n observed: string,\n expected: string,\n): Result<never, AssetEvidenceError> {\n return err({\n code,\n expected,\n hint: ASSET_EVIDENCE_ERROR_HINTS[code],\n detail: { guid, observed, expected },\n });\n}\n\nfunction distinct<T>(values: readonly T[], key: (value: T) => string): readonly T[] {\n const result: T[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const identity = key(value);\n if (!seen.has(identity)) {\n seen.add(identity);\n result.push(value);\n }\n }\n return result;\n}\n\nfunction chooseSource(\n inputs: AssetEvidenceInputs,\n): Result<SourceDeclarationEvidence | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.source, ...(inputs.sources ?? [])].filter(\n (value): value is SourceDeclarationEvidence => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-source-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one source declaration per GUID',\n );\n }\n return ok(values[0]);\n}\n\nfunction chooseLocator(\n inputs: AssetEvidenceInputs,\n): Result<AssetEvidenceLocator | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.locator, ...(inputs.locators ?? [])].filter(\n (value): value is AssetEvidenceLocator => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-locator-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one package locator per GUID',\n );\n }\n return ok(values[0]);\n}\n\nfunction chooseReceipt(\n inputs: AssetEvidenceInputs,\n): Result<CookReceipt | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.receipt, ...(inputs.receipts ?? [])].filter(\n (value): value is CookReceipt => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one cook receipt per GUID',\n );\n }\n const receipt = values[0];\n if (receipt !== undefined && receipt.guid.toLowerCase() !== inputs.guid.toLowerCase()) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n inputs.guid,\n receipt.guid,\n `receipt GUID ${inputs.guid}`,\n );\n }\n return ok(receipt);\n}\n\nfunction packageEvidence(inputs: AssetEvidenceInputs): PackageVerificationEvidence | undefined {\n if (inputs.packageVerification !== undefined) return inputs.packageVerification;\n if (inputs.package === undefined) return undefined;\n const statuses = Object.values(inputs.package.artifacts).map(\n (artifact) => artifact.verification ?? 'notChecked',\n );\n const status = statuses.some((value) => value === 'failed')\n ? 'failed'\n : statuses.length > 0 && statuses.every((value) => value === 'passed')\n ? 'passed'\n : 'notChecked';\n return {\n status,\n ...(inputs.package.digest !== undefined ? { digest: inputs.package.digest } : {}),\n };\n}\n\nfunction artifactsEvidence(\n inputs: AssetEvidenceInputs,\n): Readonly<Record<string, AssetEvidenceArtifact>> {\n const descriptors =\n inputs.artifacts ??\n Object.fromEntries(\n Object.entries(inputs.package?.artifacts ?? {}).map(([key, value]) => [\n key,\n value.descriptor,\n ]),\n );\n return Object.fromEntries(\n Object.entries(descriptors).map(([key, descriptor]) => [\n key,\n {\n descriptor,\n verification:\n inputs.artifactVerification?.[key] ??\n inputs.package?.artifacts[key]?.verification ??\n 'notChecked',\n },\n ]),\n );\n}\n\nfunction freshness(\n source: SourceDeclarationEvidence | undefined,\n receipt: CookReceipt | undefined,\n): CookFreshness {\n if (source?.origin === 'authoredPack') return 'notApplicable';\n if (receipt === undefined) return 'unknown';\n if (source?.inputFingerprint === undefined) return 'unknown';\n return source.inputFingerprint === receipt.inputFingerprint ? 'current' : 'stale';\n}\n\nfunction cookStatus(\n source: SourceDeclarationEvidence | undefined,\n receipt: CookReceipt | undefined,\n): CookStatus {\n if (source?.origin === 'authoredPack') return 'notRequired';\n if (receipt?.status === 'failed') return 'failed';\n if (receipt?.status === 'succeeded') return 'ready';\n if (source?.origin === 'sourceMeta') return 'notCooked';\n return 'unknown';\n}\n\n/** Join source, locator, receipt, package, artifact, and runtime facts into one view. */\nexport function projectAssetEvidence(\n inputs: AssetEvidenceInputs,\n): Result<AssetEvidence, AssetEvidenceError> {\n const sourceResult = chooseSource(inputs);\n if (!sourceResult.ok) return sourceResult;\n const locatorResult = chooseLocator(inputs);\n if (!locatorResult.ok) return locatorResult;\n const receiptResult = chooseReceipt(inputs);\n if (!receiptResult.ok) return receiptResult;\n\n const source = sourceResult.value;\n const locator = locatorResult.value;\n const receipt = receiptResult.value;\n const packageVerification = packageEvidence(inputs);\n if (\n receipt?.outputDigest !== undefined &&\n packageVerification?.digest !== undefined &&\n receipt.outputDigest !== packageVerification.digest\n ) {\n return conflict(\n 'asset-evidence-digest-mismatch',\n inputs.guid,\n packageVerification.digest,\n receipt.outputDigest,\n );\n }\n\n return ok({\n guid: inputs.guid,\n ...(locator?.packageUrl !== undefined ? { packageUrl: locator.packageUrl } : {}),\n ...(locator?.cookReceiptUrl !== undefined ? { cookReceiptUrl: locator.cookReceiptUrl } : {}),\n ...(source !== undefined ? { source } : {}),\n cook: {\n status: cookStatus(source, receipt),\n freshness: freshness(source, receipt),\n ...(receipt !== undefined ? { receipt } : {}),\n },\n ...(packageVerification !== undefined ? { package: packageVerification } : {}),\n artifacts: artifactsEvidence(inputs),\n runtime: { status: inputs.runtime?.status ?? 'unknown' },\n });\n}\n\nexport type {\n ArtifactVerificationStatus,\n CookFreshness,\n CookStatus,\n RuntimeEvidenceStatus,\n} from './asset.js';\nexport type { ArtifactDescriptor, ContentEncoding, Integrity };\n","// @forgeax/engine-types - Handle<T,M> brand + AssetTagMap + TagOf + 3 helpers SSOT.\n//\n// Single physical source-of-truth (feat-20260517-handle-type-unify M1 / D-2 / D-4 / D-7).\n// The package barrel `index.ts` re-exports this file; AI users import the\n// brand and helpers via `@forgeax/engine-types`, and IDE hover lands on this\n// file (charter F1 single-entry indexability).\n//\n// Contents (charter P4 consistent abstraction - 5 co-located building blocks):\n// - type Handle<T extends string, M extends 'unique' | 'shared'>\n// (double-axis phantom brand on top of `number`)\n// - type UniqueHandle<T> / SharedHandle<T> (mode-pinned aliases)\n// - interface AssetTagMap (14-member closed map mesh/texture/cube-texture/sampler/material/scene/audio/skin/skeleton/animation-clip/shader/font/render-pipeline/tileset/video)\n// - type TagOf<T extends Asset> (distributive conditional - 14+1 never tail)\n// - function toUnique<T>(raw) / toShared<T>(raw) (brand creation factories)\n// - function unwrapHandle<T,M>(h) (brand removal helper - cast inverse)\n//\n// The single `as Handle<T, M>` cast inside each factory is the brand-creation\n// structural cast (D-7 + AC-01 exemption); all other call sites must route\n// through these factories or `unwrapHandle` so that no `as unknown as Handle`\n// or `as unknown as number` literal survives anywhere outside this file\n// (AC-01 grep gate, M3 / M4 cleanup).\n//\n// Charter mapping: F1 (single-entry IDE autocomplete from\n// `@forgeax/engine-types`) + P3 (cross-mode rejection is a TS compile-time\n// failure red line) + P4 (consistent abstraction: brand + map + 3 factories\n// + 1 distributive conditional all co-located in this 1 file).\n\nimport type { Asset } from './index';\n\n/**\n * Phantom-branded Handle: a `number` carrying two type tags.\n *\n * @typeParam T - asset target tag (string literal, e.g. `'MeshAsset'`)\n * @typeParam M - release mode: `'unique'` (ECS-tracked via UniqueRefStore)\n * or `'shared'` (external owner, e.g. `AssetRegistry`)\n *\n * Runtime representation is a u32 number so the GPU upload path\n * (`GPUBuffer.writeBuffer(slot, ...)`) keeps zero-cost passthrough; only the\n * TS layer enforces non-assignability across modes / targets via the\n * `__handle` phantom field. The `__handle` field is type-only - runtime\n * objects never carry it (charter P4 zero-overhead abstraction).\n *\n * Cross-tag rejection: `Handle<'MeshAsset', M>` is not assignable to\n * `Handle<'TextureAsset', M>` and vice versa (the brand `target` field\n * differs).\n *\n * Cross-mode rejection: `Handle<T, 'unique'>` is not assignable to\n * `Handle<T, 'shared'>` and vice versa (the brand `mode` field differs);\n * this is the TS compile-time wall that prevents accidentally feeding a\n * unique-mode handle to a registry that owns its own release lifecycle (charter\n * P3 explicit failure red line; tests live in\n * `packages/types/src/__tests__/handle-brand.test-d.ts` and\n * `packages/ecs/src/__tests__/handle.test-d.ts`).\n *\n * AI users do not write `as Handle<...>` - handles come from registry\n * factories (`engine.assets.register<T>(asset).unwrap()` produces\n * `Handle<TagOf<T>, 'shared'>`; `world.uniqueRefs.alloc<T>(value)`\n * produces `Handle<T, 'unique'>` after M2). The only `as Handle` literal in the\n * codebase is the brand-creation cast inside `toUnique` / `toShared`\n * below (AC-01 exemption).\n */\nexport type Handle<T extends string, M extends 'unique' | 'shared'> = number & {\n readonly __handle: { readonly target: T; readonly mode: M };\n};\n\n/**\n * Convenience alias - unique-mode handle for asset target `T`.\n *\n * Intended for ECS-internal consumption (column slot read sites in the\n * unique-ref store, M2 rename). The `@forgeax/engine-ecs` barrel does NOT\n * re-export this alias name (AC-15 grep gate - keeps the AI-facing surface\n * narrow); callers outside ecs continue to write `Handle<T, 'unique'>`\n * literally.\n *\n * Schema vocab `'unique<T>'` derives the column field type to this alias via\n * `FieldValueType<T>` conditional inference (see\n * `packages/ecs/src/component.ts`).\n */\nexport type UniqueHandle<T extends string> = Handle<T, 'unique'>;\n\n/**\n * Convenience alias - shared-mode handle for asset target `T`.\n *\n * Mirrors `UniqueHandle<T>` for the refcounted-owner side; surfaces on\n * `AssetRegistry.register<T>` return signatures and `MeshFilter.assetHandle`\n * column type. Re-exported by the `@forgeax/engine-ecs` barrel (alongside\n * `Handle`) so AI users importing from ecs see the alias - this remains\n * subordinate to writing `Handle<T, 'shared'>` literally.\n *\n * Schema vocab `'shared<T>'` derives the column field type to this alias\n * via `FieldValueType<T>` conditional inference (feat-20260614 M5).\n */\nexport type SharedHandle<T extends string> = Handle<T, 'shared'>;\n\n/**\n * Asset.kind tag map - 13-member closed map keying each Asset variant\n * `kind` literal to its TS type name string literal (D-1 path (a)).\n *\n * Used by `TagOf<T>` distributive conditional below to derive the brand\n * `target` tag from an Asset variant TS type at register / inference time;\n * AI users adding a new Asset variant minor-add the corresponding\n * `kind -> 'XxxAsset'` row here so that `register<NewVariant>(asset)` returns\n * the correct `Handle<'XxxAsset', 'shared'>` automatically (this map is\n * the single must-edit point per Asset addition - charter F1 single-entry\n * indexability).\n *\n * The 13 members align byte-for-byte with the closed `Asset` union; adding\n * a new member to `Asset` without adding a row here surfaces as a\n * `TagOf<NewAsset>` resolving to `never` (charter P3 explicit failure -\n * downstream `register<NewAsset>` calls fail to compile).\n */\nexport interface AssetTagMap {\n mesh: 'MeshAsset';\n texture: 'TextureAsset';\n equirect: 'EquirectAsset';\n sampler: 'SamplerAsset';\n material: 'MaterialAsset';\n scene: 'SceneAsset';\n audio: 'AudioClipAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n skin: 'SkinAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n skeleton: 'SkeletonAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n 'animation-clip': 'AnimationClip';\n /** feat-20260713-animation-state-machine-plugin M2 / w13 */\n 'animation-graph': 'AnimationGraph';\n /** feat-20260528-material-shader-registration-unification M1 / w1 */\n /** feat-20260531-world-space-msdf-text-rendering M2 / w5 */\n font: 'FontAsset';\n /** feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend M1 / w5 */\n 'render-pipeline': 'RenderPipelineAsset';\n /** feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild */\n tileset: 'TilesetAsset';\n /** feat-20260623-world-space-video-asset M1 / w2 */\n video: 'VideoAsset';\n /** feat-20260728-wave1-vfx-contract-and-asset-cook M1 / m1-i1 */\n 'particle-effect': 'ParticleEffectAsset';\n}\n\n/**\n * Distributive conditional - maps an Asset variant TS type to its brand\n * `target` tag string literal (D-1 path (a)).\n *\n * `TagOf<MeshAsset>` resolves to `'MeshAsset'`; `TagOf<MaterialAsset>`\n * resolves to `'MaterialAsset'` even though `MaterialAsset` is itself the\n * pass-based single interface (MaterialAsset) - the\n * distributive conditional resolves to 'MaterialAsset' via kind: 'material'\n * collapse onto `'MaterialAsset'` because both share `kind: 'material'`\n * (research Finding 2).\n *\n * Asset variants without a matching `AssetTagMap` row (or a `kind` literal\n * outside the 5 closed values) resolve to `never`, surfacing the missing\n * row at every downstream `register<T>` consumer site (charter P3 explicit\n * failure).\n */\nexport type TagOf<T extends Asset> = T extends { kind: infer K }\n ? K extends keyof AssetTagMap\n ? AssetTagMap[K]\n : never\n : never;\n\n/**\n * Construct a `Handle<T, 'unique'>` from a raw u32. Brand-creation\n * structural cast - the `as Handle<T, 'unique'>` literal here is the\n * AC-01 exemption single point of brand creation (D-7); all other call\n * sites must route through this factory.\n *\n * Used by `World.allocUniqueRef<T>(value)` (M2 rename of allocUniqueRef)\n * to brand fresh handles that the ECS will track via the per-row release\n * loop. AI users typically\n * do not call this directly - it is the brand-creation primitive that the\n * ecs / runtime layers wrap.\n */\nexport function toUnique<T extends string>(raw: number): Handle<T, 'unique'> {\n return raw as Handle<T, 'unique'>;\n}\n\n/**\n * Construct a `Handle<T, 'shared'>` from a raw u32. Brand-creation\n * structural cast - the `as Handle<T, 'shared'>` literal here is the\n * AC-01 exemption single point of brand creation (D-7).\n *\n * Used by `AssetRegistry.register<T>(asset).unwrap()` to brand the returned handle\n * with `Handle<TagOf<T>, 'shared'>`, and by builtin handle constants\n * (`HANDLE_CUBE` / `HANDLE_TRIANGLE` / `HANDLE_ROOM_CUBE` / `BUILTIN_HANDLE_*`)\n * to brand compile-time u32 literals without caller-side `as unknown as`\n * casts (AC-05).\n */\nexport function toShared<T extends string>(raw: number): Handle<T, 'shared'> {\n return raw as Handle<T, 'shared'>;\n}\n\n/**\n * Remove the `Handle<T, M>` brand and recover the raw u32 carried inside.\n * Brand-removal helper - the inverse of `toUnique` / `toShared`.\n *\n * Runtime is identity (the brand `__handle` field is type-only; the\n * underlying number value is unchanged); the helper exists purely to\n * collapse all `as unknown as number` cast sites into a single function so\n * that AC-01 grep can surface stragglers (D-7 / D-8 cast collapse plan).\n *\n * Public on the types barrel (parallel to `toUnique` / `toShared`):\n * column read sites in unique-ref-store (M2 rename) / scene-instance-container,\n * AssetRegistry internal `Map<number, ...>` key reads, and any AI-user\n * code that needs to bridge a branded handle to a numeric ABI all call\n * this. AI users on the typical spawn-site / register-site surface\n * usually do not need it (charter P1 progressive disclosure — handle\n * stays branded end-to-end), but when a numeric escape is required this\n * is the single sanctioned escape.\n */\nexport function unwrapHandle<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return h;\n}\n\n/**\n * Slot boundary between the builtin tier and the user tier (feat-20260614 M6\n * D-15 / D-16). Builtin asset handles (the 5 process-static meshes:\n * HANDLE_CUBE=1 .. HANDLE_NINESLICE_QUAD=5) occupy slots `[1, BUILTIN_BASE)`;\n * user-tier handles minted by `World.sharedRefs.alloc` start at `BUILTIN_BASE`.\n *\n * Defined here in `@forgeax/engine-types` — the single dependency shared by\n * both `@forgeax/engine-ecs` (SharedRefStore `nextSlot` init + builtin-slot\n * fail-fast) and the package that authors process-static builtin payloads\n * dispatch + AssetRegistry index) — so the boundary is one named constant with\n * no cross-package circular dependency. Value 1024 is the historic\n * `FIRST_USER_HANDLE` literal, promoted to the shared SSOT.\n */\nexport const BUILTIN_BASE = 1024;\n\n// === Gen-slot codec SSOT (feat-20260623-asset-handle-generation M1 / w2) ==========\n//\n// Domain-agnostic pure bit operations — max-slot, max-gen, pack, unpack-slot,\n// unpack-gen, and the retire predicate (isRetiredSlot: gen > MAX_GEN, so gen\n// 255 is usable and a slot retires only when its bump would reach 256). This\n// is the single definition point\n// for the `(gen << 24) | slot` layout across entity and asset handles (D-1,\n// AC-15). Callers (ecs / runtime / ref stores) import from\n// @forgeax/engine-types; entity-side overflow throw and sentinel stay in ecs\n// (D-1). No domain concepts live here — just mask and shift.\n//\n// Bit layout (OOS-3: 32-bit number, 24-bit slot + 8-bit gen):\n// - slot: bits [0, 23], max value (1 << 24) - 1 = 16_777_215\n// - gen: bits [24, 31], max value 0xff = 255\n//\n// pack(slot, gen) fixed to (((gen & 0xff) << 24) | (slot & 0xffffff)) >>> 0\n// per D-7 hard constraint — the `>>> 0` prevents ToInt32 negative when\n// gen >= 128 (entity-handle.ts:73 comment documents this trap).\n\n/** Maximum slot index (2^24 - 1 = 16_777_215). */\nexport const MAX_SLOT = (1 << 24) - 1;\n\n/** Maximum generation value (2^8 - 1 = 255). */\nexport const MAX_GEN = 0xff;\n\n/**\n * Pack (slot, gen) into a u32 handle.\n *\n * The caller is responsible for ensuring slot does not exceed MAX_SLOT;\n * values above 24 bits are masked by `slot & 0xffffff`. Generation is\n * masked to 8 bits via `gen & 0xff`. The `>>> 0` forces unsigned u32\n * representation — without it, gen >= 128 produces a signed-negative\n * ToInt32 (D-7 hard constraint, entity-handle.ts:73).\n */\nexport function pack(slot: number, gen: number): number {\n return (((gen & 0xff) << 24) | (slot & 0xffffff)) >>> 0;\n}\n\n/** Extract the low 24 bits (slot) from a packed u32 handle. */\nexport function unpackSlot(v: number): number {\n return v & 0xffffff;\n}\n\n/** Extract the high 8 bits (generation) from a packed u32 handle. */\nexport function unpackGen(v: number): number {\n return (v >>> 24) & 0xff;\n}\n\n/**\n * Retire-when-gen-exceeds-MAX_GEN semantic: returns `true` when gen has\n * exceeded MAX_GEN (255), i.e. gen 255 is still a usable handle; a slot\n * retires only when its bumped generation reaches 256. A retired slot\n * never returns to the free list (AC-07).\n */\nexport function isRetiredSlot(gen: number): boolean {\n return gen > MAX_GEN;\n}\n\n// === Handle inspection helpers (feat-20260623-asset-handle-generation M1 / w2) ====\n//\n// Thin wrappers over the shared codec for Handle<T, M> consumers. handleSlot\n// and handleGeneration internally call unpackSlot / unpackGen — these are the\n// migration targets for unwrapHandle sites that only need the slot or gen\n// (decision q4). unwrapHandle stays as-is for sites that need the full\n// encoded value (D-5 excluded round-trip sites).\n\n/**\n * Extract the slot (low 24 bits) from a branded Handle.\n *\n * This is the runtime identity of the handle — the slot index that maps to\n * store payloads / GPU resource keys. Call sites that currently use\n * `unwrapHandle(h)` as a Map key should migrate here so the key stays stable\n * when gen > 0 (AC-09).\n */\nexport function handleSlot<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return unpackSlot(h as unknown as number);\n}\n\n/**\n * Extract the generation (high 8 bits) from a branded Handle.\n *\n * Used by store-level gen comparisons (resolve/retain/release) to detect\n * stale handles — the gen embedded during alloc is compared against the\n * store's current gen for the same slot.\n */\nexport function handleGeneration<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return unpackGen(h as unknown as number);\n}\n","import type { AssetKind } from '../asset-runtime.js';\nimport type { AssetGuid } from '../index.js';\nimport type { MaterialColorSpace } from './color-space.js';\n\nexport type MaterialParameterType =\n | 'bool'\n | 'f32'\n | 'i32'\n | 'u32'\n | 'vec2'\n | 'vec3'\n | 'vec4'\n | 'color'\n | 'texture';\n\nexport interface MaterialParameter {\n readonly name: string;\n readonly type: MaterialParameterType;\n /**\n * Asset-side transfer function for an authored color value. `color`\n * parameters default to `srgb`; numeric vectors default to linear unless\n * explicitly tagged. An asset-level `colorSpace` overrides this schema\n * default. Runtime material values are always linear.\n */\n readonly colorSpace?: MaterialColorSpace;\n readonly default?: MaterialValue;\n readonly optional?: boolean;\n readonly static?: boolean;\n}\n\nexport interface MaterialTextureCoordinates {\n readonly set?: number;\n /** Producer-owned logical-to-physical texture extent correction. */\n readonly physicalUvScale?: readonly [number, number];\n readonly transform?: {\n readonly offset?: readonly [number, number];\n readonly scale?: readonly [number, number];\n readonly rotation?: number;\n };\n}\n\nexport interface ResolvedMaterialTextureCoordinates {\n readonly set: number;\n readonly transform: {\n readonly offset: readonly [number, number];\n readonly scale: readonly [number, number];\n readonly rotation: number;\n };\n}\n\nexport function resolveMaterialTextureCoordinates(\n coordinates?: MaterialTextureCoordinates,\n): ResolvedMaterialTextureCoordinates {\n return {\n set: coordinates?.set ?? 0,\n transform: {\n offset: coordinates?.transform?.offset ?? [0, 0],\n scale: coordinates?.transform?.scale ?? [1, 1],\n rotation: coordinates?.transform?.rotation ?? 0,\n },\n };\n}\n\nexport type MaterialTextureReference = AssetGuid | number | string;\n\nexport interface MaterialTextureValue {\n readonly texture: MaterialTextureReference;\n readonly sampler?: MaterialTextureReference;\n readonly coordinates?: MaterialTextureCoordinates;\n readonly normalScale?: number;\n readonly occlusionStrength?: number;\n}\n\nexport type MaterialValue = boolean | number | readonly number[] | string | MaterialTextureValue;\n\nexport interface MaterialProgram {\n readonly module: string;\n readonly vertexEntry?: string;\n readonly fragmentEntry?: string;\n readonly moduleSlots?: Readonly<Record<string, string>>;\n}\n\nexport interface MaterialPass {\n readonly name: string;\n readonly program: MaterialProgram;\n readonly renderState?: Readonly<Record<string, unknown>>;\n}\n\nexport type MaterialPassList = readonly [MaterialPass, ...MaterialPass[]];\n\nexport interface MaterialAsset {\n readonly kind: 'material';\n /**\n * Transfer function override for all authored color parameters. Omitted\n * parameters ultimately default to sRGB.\n * Explicit `linear` is reserved for physical/imported data such as glTF\n * factors. Numeric values are never rewritten when this metadata changes.\n */\n readonly colorSpace?: MaterialColorSpace;\n readonly parent?: AssetGuid;\n readonly passes?: MaterialPassList;\n readonly parameters?: readonly MaterialParameter[];\n readonly values?: Readonly<Record<string, MaterialValue | null>>;\n}\n\nexport const materialAssetKind: AssetKind<MaterialAsset, 'material'> = {\n kind: 'material',\n} as AssetKind<MaterialAsset, 'material'>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Validate JSON-authored material descriptors at their loading boundary. */\nexport function assertMaterialAsset(\n value: unknown,\n context = 'material',\n): asserts value is MaterialAsset {\n if (!isRecord(value) || value.kind !== 'material') {\n throw new Error(`${context}: expected a material asset`);\n }\n if (\n value.colorSpace !== undefined &&\n value.colorSpace !== 'srgb' &&\n value.colorSpace !== 'linear'\n ) {\n throw new Error(`${context}: invalid colorSpace`);\n }\n if (value.passes !== undefined) {\n if (!Array.isArray(value.passes) || value.passes.length === 0) {\n throw new Error(`${context}: passes must be a non-empty array`);\n }\n for (const [index, pass] of value.passes.entries()) {\n if (!isRecord(pass) || typeof pass.name !== 'string' || !isRecord(pass.program)) {\n throw new Error(`${context}: pass ${index} is malformed`);\n }\n if (typeof pass.program.module !== 'string' || pass.program.module.length === 0) {\n throw new Error(`${context}: pass ${index} has no module identity`);\n }\n if (\n (pass.program.vertexEntry !== undefined && typeof pass.program.vertexEntry !== 'string') ||\n (pass.program.fragmentEntry !== undefined && typeof pass.program.fragmentEntry !== 'string')\n ) {\n throw new Error(`${context}: pass ${index} has malformed entry points`);\n }\n if (pass.program.moduleSlots !== undefined) {\n if (!isRecord(pass.program.moduleSlots)) {\n throw new Error(`${context}: pass ${index} has malformed module slots`);\n }\n for (const [name, slot] of Object.entries(pass.program.moduleSlots)) {\n if (typeof slot !== 'string')\n throw new Error(`${context}: module slot ${name} is not a string`);\n }\n }\n }\n }\n if (value.parameters !== undefined) {\n if (!Array.isArray(value.parameters))\n throw new Error(`${context}: parameters must be an array`);\n for (const [index, parameter] of value.parameters.entries()) {\n if (\n !isRecord(parameter) ||\n typeof parameter.name !== 'string' ||\n typeof parameter.type !== 'string'\n ) {\n throw new Error(`${context}: parameter ${index} is malformed`);\n }\n if (\n parameter.colorSpace !== undefined &&\n parameter.colorSpace !== 'srgb' &&\n parameter.colorSpace !== 'linear'\n ) {\n throw new Error(`${context}: parameter ${index} has invalid colorSpace`);\n }\n }\n }\n}\n","import type { MaterialValue } from './asset.js';\n\n/** Transfer function attached to an authored material color. */\nexport type MaterialColorSpace = 'srgb' | 'linear';\n\n/** Minimal schema shape needed to identify authored color values. */\nexport interface MaterialColorParameterSchema {\n readonly name: string;\n readonly type: string;\n readonly colorSpace?: MaterialColorSpace;\n}\n\n/** IEC 61966-2-1 sRGB electro-optical transfer function. */\nexport function srgbChannelToLinear(value: number): number {\n return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n}\n\n/** IEC 61966-2-1 inverse transfer function. */\nexport function linearChannelToSrgb(value: number): number {\n return value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055;\n}\n\n/**\n * Decode an authored RGB/RGBA value for linear runtime use.\n *\n * Only the first three color channels are transformed. Alpha and any\n * additional lanes are data and pass through unchanged.\n */\nexport function authoredColorToLinear(\n value: readonly number[],\n colorSpace: MaterialColorSpace = 'srgb',\n): number[] {\n if (colorSpace === 'linear') return [...value];\n return value.map((channel, index) => (index < 3 ? srgbChannelToLinear(channel) : channel));\n}\n\n/**\n * Resolve a material parameter's asset-side transfer-function contract.\n * `color` is an authored color and therefore defaults to sRGB. Numeric\n * vectors remain linear unless their schema explicitly marks them as colors.\n */\nexport function materialParameterColorSpace(\n parameter: MaterialColorParameterSchema,\n assetColorSpace?: MaterialColorSpace,\n): MaterialColorSpace | undefined {\n const isColor = parameter.type === 'color' || parameter.colorSpace !== undefined;\n if (!isColor) return undefined;\n return assetColorSpace ?? parameter.colorSpace ?? 'srgb';\n}\n\n/**\n * Project authored MaterialAsset values into a fresh runtime value map.\n * Asset values are never mutated, so repeated extraction cannot compound the\n * transfer function.\n */\nexport function materialValuesToLinearRuntime(\n values: Readonly<Record<string, MaterialValue | null>> | undefined,\n parameters: readonly MaterialColorParameterSchema[],\n assetColorSpace?: MaterialColorSpace,\n): Readonly<Record<string, MaterialValue | null>> {\n if (values === undefined) return {};\n const colorSpaces = new Map<string, MaterialColorSpace>();\n for (const parameter of parameters) {\n const colorSpace = materialParameterColorSpace(parameter, assetColorSpace);\n if (colorSpace !== undefined) colorSpaces.set(parameter.name, colorSpace);\n }\n\n const runtimeValues: Record<string, MaterialValue | null> = {};\n for (const [name, value] of Object.entries(values)) {\n const colorSpace = colorSpaces.get(name);\n runtimeValues[name] =\n colorSpace !== undefined && Array.isArray(value)\n ? authoredColorToLinear(value, colorSpace)\n : value;\n }\n return runtimeValues;\n}\n","export const MATERIAL_ERROR_CODES = [\n 'material-parent-not-found',\n 'material-circular-inheritance',\n 'material-no-effective-pass',\n 'material-value-unknown',\n 'material-value-type-mismatch',\n 'material-contract-program-mismatch',\n 'shader-module-id-missing',\n 'shader-module-id-duplicate',\n 'shader-module-not-found',\n 'shader-module-namespace-reserved',\n 'material-reflection-binding-mismatch',\n 'material-specialization-not-cooked',\n 'material-specialization-stale-generation',\n 'gltf-material-uv-set-missing',\n 'material-derived-interface-mismatch',\n 'material-texture-coordinate-invalid',\n 'material-payload-bounds',\n] as const;\n\nexport type MaterialErrorCode = (typeof MATERIAL_ERROR_CODES)[number];\n\nexport interface MaterialGenerationVector {\n readonly dependencies: Readonly<Record<string, number>>;\n}\n\nexport interface MaterialParentNotFoundDetail {\n readonly code: 'material-parent-not-found';\n readonly leaf: string;\n readonly missingParent: string;\n readonly chain: readonly string[];\n}\n\nexport interface MaterialCircularInheritanceDetail {\n readonly code: 'material-circular-inheritance';\n readonly leaf: string;\n readonly chain: readonly string[];\n}\n\nexport interface MaterialNoEffectivePassDetail {\n readonly code: 'material-no-effective-pass';\n readonly material: string;\n}\n\nexport interface MaterialValueUnknownDetail {\n readonly code: 'material-value-unknown';\n readonly material: string;\n readonly parameter: string;\n}\n\nexport interface MaterialValueTypeMismatchDetail {\n readonly code: 'material-value-type-mismatch';\n readonly material: string;\n readonly parameter: string;\n readonly expectedType: string;\n readonly actualType: string;\n}\n\nexport interface MaterialContractProgramMismatchDetail {\n readonly code: 'material-contract-program-mismatch';\n readonly material: string;\n readonly pass: string;\n readonly program: string;\n readonly expectedProgram: string;\n}\n\nexport interface ShaderModuleIdMissingDetail {\n readonly code: 'shader-module-id-missing';\n readonly source: string;\n}\n\nexport interface ShaderModuleIdDuplicateDetail {\n readonly code: 'shader-module-id-duplicate';\n readonly module: string;\n readonly sources: readonly string[];\n}\n\nexport interface ShaderModuleNotFoundDetail {\n readonly code: 'shader-module-not-found';\n readonly module: string;\n readonly source: string;\n}\n\nexport interface ShaderModuleNamespaceReservedDetail {\n readonly code: 'shader-module-namespace-reserved';\n readonly module: string;\n readonly namespace: string;\n}\n\nexport interface MaterialReflectionBindingMismatchDetail {\n readonly code: 'material-reflection-binding-mismatch';\n readonly material: string;\n readonly pass: string;\n readonly parameter: string;\n readonly expected: string;\n readonly actual: string;\n}\n\nexport interface MaterialSpecializationNotCookedDetail {\n readonly code: 'material-specialization-not-cooked';\n readonly material: string;\n readonly staticSelection: readonly string[];\n}\n\nexport interface MaterialSpecializationStaleGenerationDetail {\n readonly code: 'material-specialization-stale-generation';\n readonly material: string;\n readonly dependencies: readonly string[];\n readonly observed: MaterialGenerationVector;\n readonly current: MaterialGenerationVector;\n}\n\nexport interface GltfMaterialUvSetMissingDetail {\n readonly material: string;\n readonly primitive: string;\n readonly slot: string;\n readonly requestedSet: number;\n readonly availableSets: readonly number[];\n}\n\nexport interface MaterialDerivedInterfaceMismatchDetail {\n readonly code: 'material-derived-interface-mismatch';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly expectedIdentity?: string;\n readonly actualIdentity?: string;\n readonly parameter?: string;\n readonly action: 'recook';\n}\n\nexport interface MaterialTextureCoordinateInvalidDetail {\n readonly code: 'material-texture-coordinate-invalid';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly parameter: string;\n readonly slot: string;\n readonly reason: 'missing' | 'non-finite' | 'shape';\n readonly action: 'recook';\n}\n\nexport interface MaterialPayloadBoundsDetail {\n readonly code: 'material-payload-bounds';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly slot: string;\n readonly byteOffset: number;\n readonly byteLength: number;\n readonly payloadBytes: number;\n readonly action: 'stop-draw';\n}\n\ninterface MaterialErrorDetailByCode {\n readonly 'material-parent-not-found': MaterialParentNotFoundDetail;\n readonly 'material-circular-inheritance': MaterialCircularInheritanceDetail;\n readonly 'material-no-effective-pass': MaterialNoEffectivePassDetail;\n readonly 'material-value-unknown': MaterialValueUnknownDetail;\n readonly 'material-value-type-mismatch': MaterialValueTypeMismatchDetail;\n readonly 'material-contract-program-mismatch': MaterialContractProgramMismatchDetail;\n readonly 'shader-module-id-missing': ShaderModuleIdMissingDetail;\n readonly 'shader-module-id-duplicate': ShaderModuleIdDuplicateDetail;\n readonly 'shader-module-not-found': ShaderModuleNotFoundDetail;\n readonly 'shader-module-namespace-reserved': ShaderModuleNamespaceReservedDetail;\n readonly 'material-reflection-binding-mismatch': MaterialReflectionBindingMismatchDetail;\n readonly 'material-specialization-not-cooked': MaterialSpecializationNotCookedDetail;\n readonly 'material-specialization-stale-generation': MaterialSpecializationStaleGenerationDetail;\n readonly 'gltf-material-uv-set-missing': GltfMaterialUvSetMissingDetail;\n readonly 'material-derived-interface-mismatch': MaterialDerivedInterfaceMismatchDetail;\n readonly 'material-texture-coordinate-invalid': MaterialTextureCoordinateInvalidDetail;\n readonly 'material-payload-bounds': MaterialPayloadBoundsDetail;\n}\n\nexport type MaterialErrorDetail = MaterialErrorDetailByCode[MaterialErrorCode];\n\nexport type MaterialErrorFor<C extends MaterialErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: MaterialErrorDetailByCode[C];\n readonly message: string;\n};\n\nexport type MaterialError = {\n [C in MaterialErrorCode]: MaterialErrorFor<C>;\n}[MaterialErrorCode];\n\nconst MATERIAL_ERROR_POLICY = {\n 'material-parent-not-found': {\n expected: 'every parent GUID resolves to a MaterialAsset',\n hint: 'fix the parent GUID and resolve the material again',\n },\n 'material-circular-inheritance': {\n expected: 'the parent chain is acyclic',\n hint: 'remove the repeated GUID from the parent chain',\n },\n 'material-no-effective-pass': {\n expected: 'the resolved material has at least one pass',\n hint: 'add a pass to the root material or an inherited parent',\n },\n 'material-value-unknown': {\n expected: 'every value name is declared by the effective contract',\n hint: 'remove the value or declare the parameter in the root contract',\n },\n 'material-value-type-mismatch': {\n expected: 'each value matches its declared parameter type',\n hint: 'change the value to the declared parameter type',\n },\n 'material-contract-program-mismatch': {\n expected: 'the program satisfies the material contract',\n hint: 'align the program entries with the root contract',\n },\n 'shader-module-id-missing': {\n expected: 'each WGSL source declares a module ID',\n hint: 'add a compiler-native module ID declaration to the WGSL source',\n },\n 'shader-module-id-duplicate': {\n expected: 'each module ID has one source provenance',\n hint: 'rename one module or remove the duplicate source',\n },\n 'shader-module-not-found': {\n expected: 'every referenced module exists in the source catalog',\n hint: 'add the module to the source catalog or fix the reference',\n },\n 'shader-module-namespace-reserved': {\n expected: 'user modules use a non-reserved namespace',\n hint: 'choose a module ID outside the reserved namespace',\n },\n 'material-reflection-binding-mismatch': {\n expected: 'reflection matches the material contract bindings',\n hint: 'update the contract or WGSL binding and cook again',\n },\n 'material-specialization-not-cooked': {\n expected: 'the requested specialization has a cooked artifact',\n hint: 'run the build or development cook path for this selection',\n },\n 'material-specialization-stale-generation': {\n expected: 'all specialization dependencies share one generation',\n hint: 'retry after dependent assets and sources settle',\n },\n 'gltf-material-uv-set-missing': {\n expected: 'each texture slot references an available primitive UV set',\n hint: 'add the requested UV set to the primitive and re-import it',\n },\n 'material-derived-interface-mismatch': {\n expected: 'the generated material interface matches the derived schema interface',\n hint: 'repair the schema or WGSL producer and recook the material',\n },\n 'material-texture-coordinate-invalid': {\n expected: 'every texture coordinate record is finite and complete',\n hint: 'repair the texture metadata or coordinates and recook the material',\n },\n 'material-payload-bounds': {\n expected: 'every material payload write stays within the derived payload',\n hint: 'repair the derived payload owner before submitting the draw',\n },\n} satisfies {\n readonly [C in MaterialErrorCode]: {\n readonly expected: string;\n readonly hint: string;\n };\n};\n\nexport const MATERIAL_ERROR_EXPECTED: Readonly<Record<MaterialErrorCode, string>> =\n Object.fromEntries(\n MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].expected]),\n ) as Readonly<Record<MaterialErrorCode, string>>;\n\nexport const MATERIAL_ERROR_HINTS: Readonly<Record<MaterialErrorCode, string>> = Object.fromEntries(\n MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].hint]),\n) as Readonly<Record<MaterialErrorCode, string>>;\n\nexport function createMaterialError<C extends MaterialErrorCode>(\n code: C,\n detail: MaterialErrorDetailByCode[C],\n message = `${code}: ${MATERIAL_ERROR_EXPECTED[code]}`,\n): MaterialErrorFor<C> {\n return {\n code,\n expected: MATERIAL_ERROR_EXPECTED[code],\n hint: MATERIAL_ERROR_HINTS[code],\n detail,\n message,\n };\n}\n","import { err, ok, type Result } from '../result.js';\nimport type { MaterialAsset, MaterialParameter, MaterialPass, MaterialValue } from './asset.js';\nimport type { MaterialError } from './errors.js';\nimport { createMaterialError } from './errors.js';\n\nexport type MaterialTable = Readonly<Record<string, MaterialAsset>>;\n\nexport interface ResolvedMaterial {\n readonly leaf: string;\n readonly chain: readonly string[];\n readonly asset: MaterialAsset;\n}\n\nexport function materialGuidText(value: string | Uint8Array): string {\n return typeof value === 'string'\n ? value\n : Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nfunction valueType(value: MaterialValue): string {\n if (typeof value === 'boolean') return 'bool';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'string') return 'string';\n if (Array.isArray(value)) return `vec${value.length}`;\n return 'texture';\n}\n\nfunction parameterTypeMatches(parameter: MaterialParameter, value: MaterialValue): boolean {\n switch (parameter.type) {\n case 'bool':\n return typeof value === 'boolean';\n case 'f32':\n case 'i32':\n case 'u32':\n return typeof value === 'number';\n case 'vec2':\n return Array.isArray(value) && value.length === 2;\n case 'vec3':\n return Array.isArray(value) && value.length === 3;\n case 'vec4':\n case 'color':\n return Array.isArray(value) && value.length === 4;\n case 'texture':\n // Runtime asset handles are branded numbers at the type level. The\n // brand is erased before a material reaches the resolver, so numeric\n // handles must remain valid texture values alongside structured\n // texture descriptors.\n return (\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isInteger(value) && value >= 0) ||\n (typeof value === 'object' && !Array.isArray(value))\n );\n }\n}\n\nfunction validateValues(\n material: string,\n values: Readonly<Record<string, MaterialValue | null>>,\n parameters: readonly MaterialParameter[] | undefined,\n): Result<true, MaterialError> {\n if (parameters === undefined) return ok(true);\n const declarations = new Map(parameters.map((parameter) => [parameter.name, parameter]));\n for (const [name, value] of Object.entries(values)) {\n const parameter = declarations.get(name);\n if (parameter === undefined) {\n return err(\n createMaterialError('material-value-unknown', {\n code: 'material-value-unknown',\n material,\n parameter: name,\n }),\n );\n }\n if (value === null) {\n if (!parameter.optional) {\n return err(\n createMaterialError('material-value-type-mismatch', {\n code: 'material-value-type-mismatch',\n material,\n parameter: name,\n expectedType: parameter.type,\n actualType: 'null',\n }),\n );\n }\n continue;\n }\n if (!parameterTypeMatches(parameter, value)) {\n return err(\n createMaterialError('material-value-type-mismatch', {\n code: 'material-value-type-mismatch',\n material,\n parameter: name,\n expectedType: parameter.type,\n actualType: valueType(value),\n }),\n );\n }\n }\n return ok(true);\n}\n\nfunction mergePasses(\n inherited: readonly MaterialPass[] | undefined,\n override: readonly MaterialPass[] | undefined,\n): readonly MaterialPass[] | undefined {\n if (inherited === undefined && override === undefined) return undefined;\n const passes = [...(inherited ?? [])];\n for (const next of override ?? []) {\n const index = passes.findIndex((current) => current.name === next.name);\n if (index === -1) passes.push(next);\n else passes[index] = next;\n }\n return passes;\n}\n\nfunction mergeMaterial(parent: MaterialAsset, child: MaterialAsset): MaterialAsset {\n const values: Record<string, MaterialValue> = {};\n for (const [name, value] of Object.entries(parent.values ?? {})) {\n if (value !== null) values[name] = value;\n }\n for (const [name, value] of Object.entries(child.values ?? {})) {\n if (value === null) delete values[name];\n else values[name] = value;\n }\n const passes = mergePasses(parent.passes, child.passes);\n return {\n kind: 'material',\n ...((child.colorSpace ?? parent.colorSpace) !== undefined\n ? { colorSpace: child.colorSpace ?? parent.colorSpace }\n : {}),\n ...(passes !== undefined && passes.length > 0\n ? { passes: passes as NonNullable<MaterialAsset['passes']> }\n : {}),\n ...(parent.parameters !== undefined ? { parameters: parent.parameters } : {}),\n ...(Object.keys(values).length > 0 ? { values } : {}),\n };\n}\n\nfunction resolveChain(\n id: string,\n leaf: string,\n table: MaterialTable,\n stack: readonly string[],\n): Result<ResolvedMaterial, MaterialError> {\n if (stack.includes(id)) {\n return err(\n createMaterialError('material-circular-inheritance', {\n code: 'material-circular-inheritance',\n leaf,\n chain: [...stack, id],\n }),\n );\n }\n const current = table[id];\n if (current === undefined) {\n return err(\n createMaterialError('material-parent-not-found', {\n code: 'material-parent-not-found',\n leaf,\n missingParent: id,\n chain: [...stack, id],\n }),\n );\n }\n const parent = current.parent === undefined ? undefined : materialGuidText(current.parent);\n if (parent === undefined) {\n if (current.passes === undefined || current.passes.length === 0) {\n return err(\n createMaterialError('material-no-effective-pass', {\n code: 'material-no-effective-pass',\n material: leaf,\n }),\n );\n }\n const values: Record<string, MaterialValue> = {};\n for (const [name, value] of Object.entries(current.values ?? {})) {\n if (value !== null) values[name] = value;\n }\n const valid = validateValues(id, values, current.parameters);\n if (!valid.ok) return valid;\n return ok({ leaf, chain: [id], asset: { ...current, values } });\n }\n\n const parentResult = resolveChain(parent, leaf, table, [...stack, id]);\n if (!parentResult.ok) return parentResult;\n const valid = validateValues(id, current.values ?? {}, parentResult.value.asset.parameters);\n if (!valid.ok) return valid;\n const merged = mergeMaterial(parentResult.value.asset, current);\n if (merged.passes === undefined || merged.passes.length === 0) {\n return err(\n createMaterialError('material-no-effective-pass', {\n code: 'material-no-effective-pass',\n material: leaf,\n }),\n );\n }\n return ok({ leaf, chain: [...parentResult.value.chain, id], asset: merged });\n}\n\nexport function resolveMaterialAsset(\n leaf: string,\n table: MaterialTable,\n): Result<ResolvedMaterial, MaterialError> {\n return resolveChain(leaf, leaf, table, []);\n}\n","import type { CatalogDiagnostic } from './asset-producer.js';\nimport type { CatalogEntry } from './catalog.js';\n\n/** Versioned wire shape shared by the asset producer and browser consumers. */\nexport const RUNTIME_ASSET_BINDING_SCHEMA = 'runtime-asset-binding-v1' as const;\nexport const RUNTIME_CATALOG_SNAPSHOT_SCHEMA = 'runtime-catalog-snapshot-v1' as const;\n\nexport type RuntimeScopeStatus = 'unbound' | 'transitioning' | 'ready' | 'degraded' | 'unavailable';\n\n/**\n * Logical projection of one package-declared asset root into the runtime\n * catalog coordinate space. This carries no filesystem path across the wire.\n */\nexport interface RuntimeCatalogRoot {\n readonly root: string;\n readonly catalogPrefix: string;\n}\n\n/**\n * The only identity a browser-side asset consumer may use for a dev realm.\n * `scopeId` describes ownership; `generation` rejects stale browser work.\n * Filesystem roots intentionally do not cross this wire contract; catalogRoots\n * is only the logical declaration-to-catalog projection used by browser views.\n */\nexport interface RuntimeAssetBinding {\n readonly schemaVersion: typeof RUNTIME_ASSET_BINDING_SCHEMA;\n readonly gameId: string;\n readonly scopeId: string;\n readonly generation: number;\n readonly status: RuntimeScopeStatus;\n readonly catalogUrl: string;\n readonly importUrlBase: string;\n readonly packageUrlBase: string;\n readonly catalogRoots?: readonly RuntimeCatalogRoot[];\n readonly authority?: 'authoritative' | 'degraded';\n readonly diagnostics?: readonly CatalogDiagnostic[];\n}\n\nexport function isRuntimeCatalogRoots(value: unknown): value is readonly RuntimeCatalogRoot[] {\n return (\n Array.isArray(value) &&\n value.every(\n (root) =>\n root !== null &&\n typeof root === 'object' &&\n typeof (root as { root?: unknown }).root === 'string' &&\n typeof (root as { catalogPrefix?: unknown }).catalogPrefix === 'string',\n )\n );\n}\n\n/** Authority-bearing catalog response for one runtime scope. */\nexport interface RuntimeCatalogSnapshot {\n readonly schemaVersion: typeof RUNTIME_CATALOG_SNAPSHOT_SCHEMA;\n readonly scopeId: string;\n readonly generation: number;\n readonly authority: 'authoritative' | 'degraded';\n readonly entries: readonly CatalogEntry[];\n readonly diagnostics: readonly CatalogDiagnostic[];\n}\n\n/**\n * Make a route below the engine's scoped runtime namespace. The helper is\n * deliberately pure so hosts and tests cannot drift on URL construction.\n */\nexport function runtimeScopePath(\n binding: Pick<RuntimeAssetBinding, 'scopeId' | 'generation'>,\n suffix = '',\n): string {\n const normalized = suffix.length === 0 ? '' : `/${suffix.replace(/^\\/+/, '')}`;\n return `/__pack/scopes/${encodeURIComponent(binding.scopeId)}/${binding.generation}${normalized}`;\n}\n\n/**\n * Create the fixed binding used by a standalone Vite game host.\n *\n * A standalone host still has one explicit realm: its game owns the roots and\n * the browser endpoints are derived from the same scope/generation pair. The\n * optional scope override is only for a host-level test server that mounts\n * several standalone entrypoints behind one deliberately shared test realm;\n * production hosts should leave it unset.\n */\nexport function createStandaloneRuntimeAssetBinding(\n gameId: string,\n scopeId = gameId,\n basePath = '',\n): RuntimeAssetBinding {\n const normalizedBase = basePath === '/' ? '' : basePath.replace(/\\/+$/, '');\n const identity = { scopeId, generation: 1 } as const;\n const scopedPath = runtimeScopePath(identity);\n const hostPrefix = normalizedBase.startsWith('/') ? normalizedBase : `/${normalizedBase}`;\n const prefix = normalizedBase.length === 0 ? '' : hostPrefix;\n return {\n schemaVersion: RUNTIME_ASSET_BINDING_SCHEMA,\n gameId,\n scopeId,\n generation: identity.generation,\n status: 'ready',\n catalogUrl: `${prefix}${scopedPath}/catalog.json`,\n importUrlBase: `${prefix}${scopedPath}/import`,\n packageUrlBase: prefix,\n };\n}\n\nexport function runtimeScopeMatches(\n binding: Pick<RuntimeAssetBinding, 'scopeId' | 'generation'> | undefined,\n scopeId: string,\n generation: number,\n): boolean {\n return binding?.scopeId === scopeId && binding.generation === generation;\n}\n","// derive(schema) — paramSchema -> one DerivedMaterialInterface.\n// feat-20260613-material-paramschema-driven-binding M1 / w3\n//\n// Decision anchors (plan-strategy §2):\n// - D-2 single pure function, no side effect; one signature consumed by 3\n// downstream paths (BGL build / UBO record / loader-extract).\n// - D-3 consecutive numeric entries are run-merged into one UBO entry at\n// one binding slot (uniform buffer); std140-aligned offsets.\n// - D-4 every texture* family entry auto-pairs a filtering sampler at\n// binding-1 (sampler emitted FIRST, then texture); sampler /\n// sampler_comparison stay user-declared.\n// - D-7 type set is the 14-literal MaterialParamType union.\n// - D-12 empty schema is graceful: bglEntries=[] / totalBytes=0 / fields empty.\n//\n// std140 alignment table (WGSL uniform):\n// - f32 / i32 / u32 size 4 align 4\n// - vec2<f32> size 8 align 8\n// - vec3<f32> size 12 align 16\n// - vec4 / color (rgba) size 16 align 16\n// - struct round-up: totalBytes is rounded up to 16-byte alignment.\n\nimport type {\n BindGroupLayoutEntry,\n MaterialParamType,\n NumericParamType,\n ParamSchemaEntry,\n TextureBindingParamType,\n} from './index.js';\n\nconst FRAGMENT = 0x2 as GPUShaderStageFlags;\n\nconst NUMERIC_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'f32',\n 'i32',\n 'u32',\n 'vec2',\n 'vec3',\n 'vec4',\n 'color',\n]);\n\nconst TEXTURE_VIEW_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'texture2d',\n 'texture_cube',\n 'texture_depth_2d',\n 'texture_cube_array',\n]);\n\nconst SAMPLER_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'sampler',\n 'sampler_comparison',\n]);\n\nconst ALL_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n ...NUMERIC_TYPES,\n ...TEXTURE_VIEW_TYPES,\n ...SAMPLER_TYPES,\n 'storage_buffer',\n]);\n\ninterface NumericFootprint {\n readonly size: number;\n readonly align: number;\n}\n\nfunction numericFootprint(t: NumericParamType): NumericFootprint {\n switch (t) {\n case 'f32':\n case 'i32':\n case 'u32':\n return { size: 4, align: 4 };\n case 'vec2':\n return { size: 8, align: 8 };\n case 'vec3':\n return { size: 12, align: 16 };\n case 'vec4':\n case 'color':\n return { size: 16, align: 16 };\n }\n}\n\nfunction alignUp(value: number, alignment: number): number {\n return (value + alignment - 1) & ~(alignment - 1);\n}\n\ninterface TextureBglDescriptor {\n readonly sampleType: GPUTextureSampleType;\n readonly viewDimension: GPUTextureViewDimension;\n}\n\nfunction textureBglDescriptor(t: TextureBindingParamType): TextureBglDescriptor {\n switch (t) {\n case 'texture2d':\n return { sampleType: 'float', viewDimension: '2d' };\n case 'texture_cube':\n return { sampleType: 'float', viewDimension: 'cube' };\n case 'texture_depth_2d':\n return { sampleType: 'depth', viewDimension: '2d' };\n case 'texture_cube_array':\n return { sampleType: 'float', viewDimension: 'cube-array' };\n case 'sampler':\n case 'sampler_comparison':\n // Not a texture view — caller must dispatch separately. Falling through\n // here is a defensive guard; numericFootprint / sampler handler covers\n // these branches before this function is reached.\n throw new Error(`derive: textureBglDescriptor called on sampler-family type '${t}'`);\n }\n}\n\nfunction samplerBindingType(t: 'sampler' | 'sampler_comparison'): GPUSamplerBindingType {\n return t === 'sampler' ? 'filtering' : 'comparison';\n}\n\n/** Single UBO sub-entry — one merged std140 slot. */\nexport interface UboFieldLayout {\n readonly name: string;\n readonly offset: number;\n readonly size: number;\n readonly type: NumericParamType;\n}\n\nexport interface DerivedNumericMember extends UboFieldLayout {\n readonly alignment: number;\n}\n\nexport interface MaterialCoordinateRecordLayout {\n readonly parameter: string;\n readonly offset: number;\n readonly size: 32;\n readonly alignment: 16;\n readonly transformMember: string;\n readonly metadataMember: string;\n}\n\nexport type MaterialResourceKind = 'sampler' | 'texture' | 'storage-buffer';\n\nexport interface MaterialResourceBindingLayout {\n readonly name: string;\n readonly parameter?: string;\n readonly kind: MaterialResourceKind;\n readonly binding: number;\n}\n\nexport interface MaterialParameterResourceProjection {\n readonly kind: 'sampler' | 'storage-buffer';\n readonly name: string;\n readonly type: 'sampler' | 'sampler_comparison' | 'storage_buffer';\n readonly resource: MaterialResourceBindingLayout;\n readonly coordinates?: never;\n}\n\nexport interface MaterialParameterTextureProjection {\n readonly kind: 'texture';\n readonly name: string;\n readonly type: TextureBindingParamType;\n readonly coordinates: MaterialCoordinateRecordLayout;\n readonly resource: {\n readonly parameter: string;\n readonly texture: MaterialResourceBindingLayout;\n readonly sampler: MaterialResourceBindingLayout;\n };\n}\n\nexport interface MaterialParameterNumericProjection {\n readonly kind: 'numeric';\n readonly name: string;\n readonly type: NumericParamType;\n readonly member: DerivedNumericMember;\n readonly coordinates?: never;\n readonly resource?: never;\n}\n\nexport type MaterialParameterProjection =\n | MaterialParameterNumericProjection\n | MaterialParameterTextureProjection\n | MaterialParameterResourceProjection;\n\nexport interface DerivedMaterialInterface {\n readonly bglEntries: readonly BindGroupLayoutEntry[];\n readonly uboLayout: UboLayout;\n readonly numericMembers: readonly DerivedNumericMember[];\n readonly coordinateRecords: readonly MaterialCoordinateRecordLayout[];\n readonly resourceBindings: readonly MaterialResourceBindingLayout[];\n readonly totalBytes: number;\n readonly layoutIdentity: string;\n readonly textureFieldNames: ReadonlySet<string>;\n readonly samplerForTexture: ReadonlyMap<string, string>;\n readonly userRegionBindingEnd: number;\n}\n\nexport interface UboLayout {\n readonly entries: readonly UboFieldLayout[];\n readonly totalBytes: number;\n}\n\nexport type DeriveOutput = DerivedMaterialInterface;\n\nexport interface ImmutableParamSchemaProjection {\n readonly ownerId: string;\n readonly revision: number;\n readonly schema: readonly ParamSchemaEntry[];\n readonly derivedInterface: DerivedMaterialInterface;\n}\n\nexport interface ParamSchemaProjectionOwnerStats {\n readonly admissions: number;\n readonly derivations: number;\n readonly projections: number;\n}\n\nexport type ParamSchemaDeriveObservationKind =\n | 'admitted-identity-hit'\n | 'unregistered-fallback-derive'\n | 'fallback-layout-sha';\n\nexport interface ParamSchemaDeriveObserver {\n readonly enabled: boolean;\n readonly observe: (event: {\n readonly kind: ParamSchemaDeriveObservationKind;\n readonly site: string;\n }) => void;\n}\n\ninterface OwnedParamSchemaProjection extends ImmutableParamSchemaProjection {\n readonly canonicalSchema: string;\n}\n\n// Runtime consumers can still receive the schema array through older API\n// surfaces. Admission binds that immutable array identity back to the one\n// owner projection, so those compatibility calls return the admitted result\n// without deriving or hashing again. Unregistered schemas keep the pure\n// offline/compiler behavior.\nconst ADMITTED_PARAM_SCHEMA_PROJECTIONS = new WeakMap<\n readonly ParamSchemaEntry[],\n DerivedMaterialInterface\n>();\n\n/**\n * Immutable/revision owner for runtime ParamSchema projections.\n *\n * A new `(ownerId, revision)` performs exactly one pure derivation. Repeating\n * the same admission is idempotent when the schema bytes match and fails loud\n * when a revision is reused for different schema content. The admitted schema\n * is cloned and frozen so caller-side mutation cannot alter a published\n * revision.\n */\nexport class ParamSchemaProjectionOwner {\n readonly #projections = new Map<string, OwnedParamSchemaProjection>();\n #admissions = 0;\n #derivations = 0;\n\n admit(args: {\n readonly ownerId: string;\n readonly revision: number;\n readonly schema: readonly ParamSchemaEntry[];\n }): ImmutableParamSchemaProjection {\n if (args.ownerId.length === 0) {\n throw new Error('ParamSchemaProjectionOwner: ownerId must be non-empty');\n }\n if (!Number.isSafeInteger(args.revision) || args.revision < 1) {\n throw new Error('ParamSchemaProjectionOwner: revision must be a positive safe integer');\n }\n this.#admissions += 1;\n const key = `${args.ownerId}\\u0000${args.revision}`;\n const canonicalSchema = JSON.stringify(args.schema);\n const existing = this.#projections.get(key);\n if (existing !== undefined) {\n if (existing.canonicalSchema !== canonicalSchema) {\n throw new Error(\n `ParamSchemaProjectionOwner: ${args.ownerId}@${args.revision} reused with different schema content`,\n );\n }\n return existing;\n }\n\n const schema = freezeParamSchema(args.schema);\n const derivedInterface = freezeDerivedMaterialInterface(derivePure(schema));\n this.#derivations += 1;\n ADMITTED_PARAM_SCHEMA_PROJECTIONS.set(schema, derivedInterface);\n const projection = Object.freeze({\n ownerId: args.ownerId,\n revision: args.revision,\n schema,\n derivedInterface,\n canonicalSchema,\n });\n this.#projections.set(key, projection);\n return projection;\n }\n\n get(ownerId: string, revision: number): ImmutableParamSchemaProjection | undefined {\n return this.#projections.get(`${ownerId}\\u0000${revision}`);\n }\n\n stats(): ParamSchemaProjectionOwnerStats {\n return Object.freeze({\n admissions: this.#admissions,\n derivations: this.#derivations,\n projections: this.#projections.size,\n });\n }\n}\n\n/**\n * Pure derivation: paramSchema -> BGL entries + UBO byte layout + field maps.\n *\n * The function has no side effects and is the SSOT for BGL / UBO / loader\n * lookup tables (D-2). Runtime / vite-plugin-shader / loader all call into\n * this single entry point; in particular, `userRegionBindingEnd` is the\n * post-user-region binding index that engine-injected groups (shadow / IBL\n * / lightmap, see D-6) must start from.\n *\n * Throws on schema authoring errors:\n * - duplicate entry name (numeric or non-numeric)\n * - unrecognised type literal (not in the 14-member union)\n * - empty entry name\n * - user-declared name collides with the auto-paired `<tex>_sampler`\n */\nexport function derive(schema: readonly ParamSchemaEntry[]): DeriveOutput {\n const admitted = ADMITTED_PARAM_SCHEMA_PROJECTIONS.get(schema);\n if (admitted !== undefined) return admitted;\n return derivePure(schema);\n}\n\n/**\n * Debug-only observation seam for runtime compatibility consumers.\n *\n * This wrapper deliberately does not cache or change derivation semantics. It\n * only records whether the supplied schema identity was admitted by the\n * ParamSchemaProjectionOwner before delegating to the existing pure `derive`\n * function. Keeping the observer out of the normal `derive` signature leaves\n * production callers on the existing zero-argument path.\n */\nexport function deriveObserved(\n schema: readonly ParamSchemaEntry[],\n observer: ParamSchemaDeriveObserver,\n site: string,\n): DeriveOutput {\n const admitted = ADMITTED_PARAM_SCHEMA_PROJECTIONS.has(schema);\n const output = derive(schema);\n if (observer.enabled) {\n observer.observe({\n kind: admitted ? 'admitted-identity-hit' : 'unregistered-fallback-derive',\n site,\n });\n if (!admitted) observer.observe({ kind: 'fallback-layout-sha', site });\n }\n return output;\n}\n\nfunction derivePure(schema: readonly ParamSchemaEntry[]): DeriveOutput {\n const bglEntries: BindGroupLayoutEntry[] = [];\n const uboFields: UboFieldLayout[] = [];\n const numericMembers: DerivedNumericMember[] = [];\n const coordinateRecords: MaterialCoordinateRecordLayout[] = [];\n const resourceBindings: MaterialResourceBindingLayout[] = [];\n const textureFieldNames = new Set<string>();\n const samplerForTexture = new Map<string, string>();\n const seenNames = new Set<string>();\n const reservedSamplerNames = new Set<string>();\n\n let nextBinding = 0;\n let uboCursor = 0;\n let uboBinding: number | null = null;\n\n const ensureUboBinding = (): number => {\n if (uboBinding !== null) return uboBinding;\n uboBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: uboBinding,\n visibility: FRAGMENT,\n buffer: { type: 'uniform' },\n });\n return uboBinding;\n };\n\n for (const rawEntry of schema) {\n const entry = rawEntry as ParamSchemaEntry;\n if (entry.name.length === 0) {\n throw new Error('derive: schema entry name must be non-empty');\n }\n if (!ALL_TYPES.has(entry.type)) {\n throw new Error(`derive: unrecognised paramSchema type literal '${entry.type}'`);\n }\n if (seenNames.has(entry.name)) {\n throw new Error(`derive: duplicate paramSchema entry name '${entry.name}'`);\n }\n if (reservedSamplerNames.has(entry.name)) {\n throw new Error(\n `derive: paramSchema entry '${entry.name}' collides with auto-paired sampler name`,\n );\n }\n seenNames.add(entry.name);\n\n if (NUMERIC_TYPES.has(entry.type)) {\n const numericType = entry.type as NumericParamType;\n const { size, align } = numericFootprint(numericType);\n ensureUboBinding();\n const offset = alignUp(uboCursor, align);\n uboFields.push({ name: entry.name, offset, size, type: numericType });\n numericMembers.push({ name: entry.name, offset, size, alignment: align, type: numericType });\n uboCursor = offset + size;\n continue;\n }\n\n if (TEXTURE_VIEW_TYPES.has(entry.type)) {\n const texType = entry.type as TextureBindingParamType;\n ensureUboBinding();\n const coordinateOffset = alignUp(uboCursor, 16);\n const coordinates: MaterialCoordinateRecordLayout = {\n parameter: entry.name,\n offset: coordinateOffset,\n size: 32,\n alignment: 16,\n transformMember: `${entry.name}CoordinatesTransform`,\n metadataMember: `${entry.name}CoordinatesMetadata`,\n };\n coordinateRecords.push(coordinates);\n uboCursor = coordinateOffset + coordinates.size;\n const { sampleType, viewDimension } = textureBglDescriptor(texType);\n // Sampler-first per plan §D-4: emit auto-paired filtering sampler at\n // binding N, then the texture view at binding N+1. Matches the actual\n // WGSL @binding declaration order in the 5 built-in shaders (sampler\n // declared on the odd binding, texture on the even+1 binding).\n const samplerName = `${entry.name}_sampler`;\n if (seenNames.has(samplerName)) {\n throw new Error(\n `derive: auto-paired sampler name '${samplerName}' collides with existing entry`,\n );\n }\n reservedSamplerNames.add(samplerName);\n samplerForTexture.set(entry.name, samplerName);\n const samplerBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: samplerBinding,\n visibility: FRAGMENT,\n sampler: { type: 'filtering' },\n });\n resourceBindings.push({\n name: samplerName,\n parameter: entry.name,\n kind: 'sampler',\n binding: samplerBinding,\n });\n\n const texBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: texBinding,\n visibility: FRAGMENT,\n texture: { sampleType, viewDimension, multisampled: false },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'texture',\n binding: texBinding,\n });\n textureFieldNames.add(entry.name);\n continue;\n }\n\n if (SAMPLER_TYPES.has(entry.type)) {\n const samplerType = entry.type as 'sampler' | 'sampler_comparison';\n const samplerBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: samplerBinding,\n visibility: FRAGMENT,\n sampler: { type: samplerBindingType(samplerType) },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'sampler',\n binding: samplerBinding,\n });\n continue;\n }\n\n // entry.type === 'storage_buffer'\n const storageBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: storageBinding,\n visibility: FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'storage-buffer',\n binding: storageBinding,\n });\n }\n\n const totalBytes = uboCursor === 0 ? 0 : alignUp(uboCursor, 16);\n const layoutIdentity = sha256LayoutIdentity({\n numericMembers,\n coordinateRecords,\n resourceBindings,\n totalBytes,\n });\n\n return {\n bglEntries,\n uboLayout: { entries: uboFields, totalBytes },\n numericMembers,\n coordinateRecords,\n resourceBindings,\n totalBytes,\n layoutIdentity,\n textureFieldNames,\n samplerForTexture,\n userRegionBindingEnd: nextBinding,\n };\n}\n\nfunction freezeParamSchema(schema: readonly ParamSchemaEntry[]): readonly ParamSchemaEntry[] {\n return Object.freeze(\n schema.map((entry) => {\n const defaultValue = cloneAndFreezeSchemaValue(entry.default);\n return Object.freeze({\n ...entry,\n ...(entry.default === undefined ? {} : { default: defaultValue }),\n }) as ParamSchemaEntry;\n }),\n );\n}\n\nfunction cloneAndFreezeSchemaValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return Object.freeze(value.map((item) => cloneAndFreezeSchemaValue(item)));\n }\n if (typeof value === 'object' && value !== null) {\n return Object.freeze(\n Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, cloneAndFreezeSchemaValue(item)]),\n ),\n );\n }\n return value;\n}\n\nfunction freezeDerivedMaterialInterface(\n derived: DerivedMaterialInterface,\n): DerivedMaterialInterface {\n deepFreeze(derived.bglEntries);\n deepFreeze(derived.uboLayout);\n deepFreeze(derived.numericMembers);\n deepFreeze(derived.coordinateRecords);\n deepFreeze(derived.resourceBindings);\n return Object.freeze({\n ...derived,\n textureFieldNames: new ImmutableSetView(derived.textureFieldNames),\n samplerForTexture: new ImmutableMapView(derived.samplerForTexture),\n });\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value !== 'object' || value === null || Object.isFrozen(value)) return value;\n for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child);\n return Object.freeze(value);\n}\n\nclass ImmutableSetView<T> implements ReadonlySet<T> {\n readonly #values: Set<T>;\n\n constructor(values: Iterable<T>) {\n this.#values = new Set(values);\n Object.freeze(this);\n }\n\n get size(): number {\n return this.#values.size;\n }\n\n has(value: T): boolean {\n return this.#values.has(value);\n }\n\n entries(): SetIterator<[T, T]> {\n return this.#values.entries();\n }\n\n keys(): SetIterator<T> {\n return this.#values.keys();\n }\n\n values(): SetIterator<T> {\n return this.#values.values();\n }\n\n forEach(callbackfn: (value: T, value2: T, set: ReadonlySet<T>) => void, thisArg?: unknown): void {\n for (const value of this.#values) callbackfn.call(thisArg, value, value, this);\n }\n\n [Symbol.iterator](): SetIterator<T> {\n return this.#values[Symbol.iterator]();\n }\n}\n\nclass ImmutableMapView<K, V> implements ReadonlyMap<K, V> {\n readonly #values: Map<K, V>;\n\n constructor(values: Iterable<readonly [K, V]>) {\n this.#values = new Map(values);\n Object.freeze(this);\n }\n\n get size(): number {\n return this.#values.size;\n }\n\n get(key: K): V | undefined {\n return this.#values.get(key);\n }\n\n has(key: K): boolean {\n return this.#values.has(key);\n }\n\n entries(): MapIterator<[K, V]> {\n return this.#values.entries();\n }\n\n keys(): MapIterator<K> {\n return this.#values.keys();\n }\n\n values(): MapIterator<V> {\n return this.#values.values();\n }\n\n forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: unknown): void {\n for (const [key, value] of this.#values) callbackfn.call(thisArg, value, key, this);\n }\n\n [Symbol.iterator](): MapIterator<[K, V]> {\n return this.#values[Symbol.iterator]();\n }\n}\n\nexport function inferMaterialParameterKind(\n entry: ParamSchemaEntry,\n): MaterialParameterProjection['kind'] {\n if (NUMERIC_TYPES.has(entry.type)) return 'numeric';\n if (TEXTURE_VIEW_TYPES.has(entry.type)) return 'texture';\n if (entry.type === 'storage_buffer') return 'storage-buffer';\n return 'sampler';\n}\n\nfunction sha256LayoutIdentity(value: {\n readonly numericMembers: readonly DerivedNumericMember[];\n readonly coordinateRecords: readonly MaterialCoordinateRecordLayout[];\n readonly resourceBindings: readonly MaterialResourceBindingLayout[];\n readonly totalBytes: number;\n}): string {\n const canonical = JSON.stringify({\n version: 1,\n numericMembers: value.numericMembers,\n coordinateRecords: value.coordinateRecords,\n resourceBindings: value.resourceBindings,\n totalBytes: value.totalBytes,\n });\n return `sha256-${sha256(canonical)}`;\n}\n\nconst SHA256_K = [\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n] as const;\n\nfunction sha256(input: string): string {\n const bytes = new TextEncoder().encode(input);\n const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;\n const padded = new Uint8Array(paddedLength);\n padded.set(bytes);\n padded[bytes.length] = 0x80;\n const view = new DataView(padded.buffer);\n view.setUint32(paddedLength - 4, bytes.length * 8, false);\n let hash = new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n ]);\n for (let block = 0; block < padded.length; block += 64) {\n const words = new Uint32Array(64);\n for (let index = 0; index < 16; index += 1)\n words[index] = view.getUint32(block + index * 4, false);\n for (let index = 16; index < 64; index += 1) {\n const a = words[index - 15] ?? 0;\n const b = words[index - 2] ?? 0;\n words[index] =\n (smallSigma1(b) + (words[index - 7] ?? 0) + smallSigma0(a) + (words[index - 16] ?? 0)) >>>\n 0;\n }\n let a = hash[0] ?? 0;\n let b = hash[1] ?? 0;\n let c = hash[2] ?? 0;\n let d = hash[3] ?? 0;\n let e = hash[4] ?? 0;\n let f = hash[5] ?? 0;\n let g = hash[6] ?? 0;\n let h = hash[7] ?? 0;\n for (let index = 0; index < 64; index += 1) {\n const choose = (e & f) ^ (~e & g);\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const t1 = (h + bigSigma1(e) + choose + (SHA256_K[index] ?? 0) + (words[index] ?? 0)) >>> 0;\n const t2 = (bigSigma0(a) + majority) >>> 0;\n [h, g, f, e, d, c, b, a] = [g, f, e, (d + t1) >>> 0, c, b, a, (t1 + t2) >>> 0];\n }\n const state = [a, b, c, d, e, f, g, h];\n hash = new Uint32Array(hash.map((value, index) => ((value + (state[index] ?? 0)) >>> 0) >>> 0));\n }\n return Array.from(hash, (word) => word.toString(16).padStart(8, '0')).join('');\n}\n\nfunction rotateRight(value: number, shift: number): number {\n return (value >>> shift) | (value << (32 - shift));\n}\n\nfunction bigSigma0(value: number): number {\n return rotateRight(value, 2) ^ rotateRight(value, 13) ^ rotateRight(value, 22);\n}\n\nfunction bigSigma1(value: number): number {\n return rotateRight(value, 6) ^ rotateRight(value, 11) ^ rotateRight(value, 25);\n}\n\nfunction smallSigma0(value: number): number {\n return rotateRight(value, 7) ^ rotateRight(value, 18) ^ (value >>> 3);\n}\n\nfunction smallSigma1(value: number): number {\n return rotateRight(value, 17) ^ rotateRight(value, 19) ^ (value >>> 10);\n}\n\n/**\n * The three user-region material texture fields whose handles flow through the\n * paramSchema-driven extract filter (`validateTextureHandle`). They map to the\n * fixed `@group(1) @binding(2/4/6)` slots in the standard material BGL.\n *\n * `emissiveTexture` / `occlusionTexture` are deliberately EXCLUDED: they live\n * in the engine-managed lightmap injection region (`appendInjection`,\n * bindings 14..17), are sampled by `default-standard-pbr` without a schema\n * entry, and are never filtered by `validateTextureHandle`. Including them\n * would false-positive the engine's own PBR shader.\n */\nconst USER_REGION_TEXTURE_FIELDS: readonly string[] = [\n 'baseColorTexture',\n 'metallicRoughnessTexture',\n 'normalTexture',\n];\n\n/** Strip `//` line comments and block comments before scanning WGSL source. */\nfunction stripWgslComments(source: string): string {\n return source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/[^\\n]*/g, '');\n}\n\n/**\n * Detect user-region material textures a shader actually `textureSample`s but\n * its paramSchema fails to declare as a texture entry.\n *\n * This is the runtime (register-time) counterpart of the build-time superset\n * gate (`compareParamSchemaSuperset`): user shaders registered directly via\n * `ShaderRegistry.installMaterialArtifact` bypass the vite-plugin-shader\n * reflection path, so an under-declared schema would otherwise let the extract\n * stage's `validateTextureHandle` silently drop the sampled texture's handle\n * and fall back to the default white texture (charter P3 violation — the bug\n * that turned the LearnOpenGL 4.3 blending demo's grass + windows opaque white;\n * see docs/handover/2026-06-19-blending-transparency-regression-bisect.md).\n *\n * Returns the field names that are sampled-but-undeclared (empty = consistent).\n * Scan is name-based on the WGSL var passed as the first `textureSample*`\n * argument; comments are stripped first so a commented-out sample never trips\n * the check. Only the three `USER_REGION_TEXTURE_FIELDS` participate, so a\n * shader that merely *declares* the standard binding layout without sampling it\n * (e.g. an outline / depth-viz shader reusing the PBR BGL) is not flagged.\n */\nexport function findUndeclaredSampledTextures(\n wgslSource: string,\n schema: readonly ParamSchemaEntry[],\n): readonly string[] {\n const declared = derive(schema).textureFieldNames;\n const clean = stripWgslComments(wgslSource);\n const sampleRe = /textureSample[A-Za-z]*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)/g;\n const sampled = new Set<string>();\n for (let m = sampleRe.exec(clean); m !== null; m = sampleRe.exec(clean)) {\n const name = m[1];\n if (name !== undefined) sampled.add(name);\n }\n return USER_REGION_TEXTURE_FIELDS.filter((f) => sampled.has(f) && !declared.has(f));\n}\n","// @forgeax/engine-types - producer-owned asset catalog contract.\n//\n// This is the shared POD boundary between an engine asset producer and any\n// consumer. Consumers must use these facts instead of deriving origin from a\n// DDC URL, filename suffix, or catalog position.\n\nexport type AssetSubjectType = 'asset' | 'package' | 'resource';\n\n/** The producer-owned subject behind a catalog row. */\nexport type CatalogSubject = 'internal-asset' | 'imported-output';\n\n/** Whether the runtime projection is validated directly or cooked. */\nexport type CookExecution = 'direct' | 'cooked';\n\n/** Derived lifecycle states exposed by the catalog. */\nexport type CatalogLifecycle = 'missing' | 'cooking' | 'current' | 'stale' | 'failed';\n\n/** Usage derived from producer refs and build-time content reads. */\nexport type AssetPublicationEvidenceUsage = 'reference' | 'content' | 'both';\n\n/** One external dependency observed while publishing a source package. */\nexport interface AssetPublicationExternalEvidence {\n readonly guid: string;\n readonly usage: AssetPublicationEvidenceUsage;\n readonly generation?: number;\n readonly digest?: string;\n}\n\n/** One ordinary asset output in an atomic source-package publication. */\nexport interface AssetPublicationOutput {\n readonly guid: string;\n readonly sourceKey: string;\n readonly kind: string;\n readonly digest: string;\n /** GUIDs emitted by the owning ordinary producer, in stable order. */\n readonly refs: readonly string[];\n}\n\n/** Receipt facts that prove the complete output set and dependency closure. */\nexport interface AssetPublicationReceipt {\n readonly schemaVersion: 'asset-publication-receipt/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly inputFingerprint: string;\n readonly outputDigest: string;\n readonly outputSetDigest: string;\n readonly externalEvidence: readonly AssetPublicationExternalEvidence[];\n}\n\n/** Stable locator for current or last-known-good publication recovery. */\nexport interface AssetPublicationLocator {\n readonly generation: number;\n readonly digest: string;\n readonly outputSetDigest: string;\n readonly packageUrl: string;\n readonly receiptKey: string;\n}\n\nexport type AssetPublicationFailureStage =\n | 'source'\n | 'output'\n | 'receipt'\n | 'route'\n | 'catalog'\n | 'cancelled';\n\n/** Machine-readable failure and recovery facts; messages are not a protocol. */\nexport interface AssetPublicationFailure {\n readonly code: string;\n readonly stage: AssetPublicationFailureStage;\n readonly sourcePath: string;\n readonly sourceRevision?: string;\n readonly generation?: number;\n readonly outputGuid?: string;\n readonly reason: string;\n}\n\n/** Actions a consumer can execute after a candidate publication is rejected. */\nexport interface AssetPublicationRecovery {\n readonly retryable: boolean;\n readonly preserveCurrent: boolean;\n readonly useLastKnownGood: boolean;\n readonly actions: readonly string[];\n}\n\n/**\n * Engine-owned publication SSOT shared by pack, import, Catalog, and consumers.\n * A publication is valid only when receipt and every output belong to one\n * source revision, generation, digest, and output-set digest tuple.\n */\nexport interface AssetPublicationEnvelope {\n readonly schemaVersion: 'asset-publication/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly generation: number;\n readonly digest: string;\n readonly outputSetDigest: string;\n readonly outputs: readonly AssetPublicationOutput[];\n readonly receipt: AssetPublicationReceipt;\n readonly externalEvidence: readonly AssetPublicationExternalEvidence[];\n readonly failureStage?: AssetPublicationFailureStage;\n readonly failure?: AssetPublicationFailure;\n readonly current?: AssetPublicationLocator;\n readonly lastKnownGood?: AssetPublicationLocator;\n readonly recovery?: AssetPublicationRecovery;\n}\n\n/**\n * Immutable source-level identity carried by an authored generated Scene mount.\n * A mount is consumable only when every member resolves from this complete\n * publication tuple; no single output GUID or generation number is sufficient.\n */\nexport interface ScenePublicationFence {\n readonly schemaVersion: 'scene-publication-fence/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly publicationGeneration: number;\n readonly outputDigest: string;\n readonly outputSetDigest: string;\n readonly receiptIdentity: string;\n}\n\nexport type CatalogOperationName =\n | 'preview'\n | 'save'\n | 'rebuild'\n | 'sourceOverride'\n | 'instanceOverride'\n | 'promote';\n\nexport interface CatalogOperationDescriptor {\n readonly operation: CatalogOperationName;\n readonly enabled: boolean;\n readonly reason?: string;\n}\n\nexport type CatalogOperations = Readonly<Record<CatalogOperationName, CatalogOperationDescriptor>>;\n\nexport interface CatalogProjectionInput {\n readonly subject: CatalogSubject;\n readonly execution: CookExecution;\n readonly lifecycle: CatalogLifecycle;\n}\n\n/** The explicit three-axis projection consumed by AI-facing catalog clients. */\nexport interface CatalogProjection extends CatalogProjectionInput {\n readonly operations: CatalogOperations;\n readonly lastKnownGood?: {\n readonly packageUrl: string;\n readonly receiptUrl?: string;\n };\n}\n\n/**\n * Derive operation descriptors from catalog facts only.\n *\n * `kind`, paths, and diagnostic messages are intentionally absent from this\n * function: a consumer receives a complete operation matrix and can branch\n * on `enabled` without reimplementing producer policy.\n */\nexport function catalogOperationsFor(input: CatalogProjectionInput): CatalogOperations {\n const imported = input.subject === 'imported-output';\n const current = input.lifecycle === 'current';\n const ready = current && (input.execution === 'direct' || input.execution === 'cooked');\n const canRebuild = input.execution === 'cooked';\n const canPreview = input.execution === 'cooked' && input.lifecycle !== 'missing';\n const operation = (name: CatalogOperationName, enabled: boolean, reason?: string) => ({\n operation: name,\n enabled,\n ...(reason === undefined ? {} : { reason }),\n });\n\n return {\n preview: operation('preview', canPreview, canPreview ? undefined : 'no projection to preview'),\n save: operation(\n 'save',\n !imported && input.execution === 'direct' && ready,\n imported ? 'imported output is read-only' : 'direct projection is not current',\n ),\n rebuild: operation(\n 'rebuild',\n canRebuild,\n canRebuild ? undefined : 'direct assets do not require a cook',\n ),\n sourceOverride: operation(\n 'sourceOverride',\n imported && canRebuild,\n imported\n ? canRebuild\n ? undefined\n : 'cooked projection is not available'\n : 'only imported output has a source override',\n ),\n instanceOverride: operation(\n 'instanceOverride',\n imported && current,\n imported\n ? current\n ? undefined\n : 'projection is not current'\n : 'only imported output has an instance override',\n ),\n promote: operation(\n 'promote',\n imported && current,\n imported\n ? current\n ? undefined\n : 'projection is not current'\n : 'internal assets are already authored',\n ),\n };\n}\n\n/** Reject impossible axis combinations before a catalog row is published. */\nexport function isCatalogProjectionValid(input: CatalogProjection): boolean {\n if (input.execution === 'direct' && input.lifecycle !== 'current') return false;\n if (input.subject === 'imported-output' && input.execution !== 'cooked') return false;\n return Object.entries(input.operations).every(\n ([name, descriptor]) => name === descriptor.operation,\n );\n}\n\n/** Structured reason for an authoring capability that is not available. */\nexport type AssetAuthoringUnavailableCode =\n | 'unsupported-asset-kind'\n | 'missing-producer-capability';\n\nexport interface AssetAuthoringUnavailableReason {\n readonly code: AssetAuthoringUnavailableCode;\n readonly hint: string;\n}\n\n/** Engine-facing operation shape exposed by a producer-owned catalog row. */\nexport type AssetPlacementCapability =\n | { readonly operation: 'spawnEntity' }\n | { readonly operation: 'addSceneAssetToScene' }\n | { readonly operation: 'unavailable'; readonly reason: AssetAuthoringUnavailableReason };\n\nexport interface AssetBindingTarget {\n readonly component: string;\n readonly field: string;\n readonly assetType: string;\n readonly cardinality: 'single' | 'array';\n}\n\n/**\n * One projection exposed by the producer-owned UI authoring contract.\n *\n * `supported` describes a real engine seam; `unavailable` is deliberately\n * structured so an editor or AI client cannot silently invent a consumer-side\n * implementation for a missing producer capability.\n */\nexport type UiAuthoringProjection =\n | {\n readonly status: 'supported';\n readonly operation:\n | 'createUiPreviewSession'\n | 'mountUi'\n | 'gameProjection'\n | 'dom-native'\n | 'ui-artifact-companion';\n readonly contractVersion: '1';\n }\n | {\n readonly status: 'unavailable';\n readonly reason: AssetAuthoringUnavailableReason;\n };\n\n/**\n * Versioned UI authoring facts published beside every `kind: 'ui'` catalog\n * row. This is a protocol descriptor, not a promise that the editor may reach\n * into a game world: runtime state/action/read semantics remain owned by the\n * game projection registrar and the UI mount/preview seams remain owned by the\n * engine UI package.\n */\nexport interface UiAuthoringCapability {\n readonly contractVersion: '1';\n readonly profileVersion: '1';\n readonly preview: {\n readonly operation: 'createUiPreviewSession';\n readonly lifecycle: 'open-rebuild-retry-dispose';\n };\n readonly mount: {\n readonly operation: 'mountUi';\n readonly lifecycle: 'mount-dispose';\n readonly actionPort: 'onAction';\n };\n readonly state: UiAuthoringProjection;\n readonly actions: UiAuthoringProjection;\n readonly reads: UiAuthoringProjection;\n readonly input: UiAuthoringProjection;\n readonly navigation: UiAuthoringProjection;\n readonly font: UiAuthoringProjection;\n readonly localization: UiAuthoringProjection;\n}\n\nexport type AssetBindingCapability =\n | {\n readonly operation: 'bindAssetRef' | 'createMaterialThenBindAssetRef';\n readonly target: AssetBindingTarget;\n readonly requiredSlots: 1;\n }\n | { readonly operation: 'unavailable'; readonly reason: AssetAuthoringUnavailableReason };\n\n/** Producer-owned placement and binding facts for one catalog asset. */\nexport interface AssetAuthoringCapability {\n readonly placement: AssetPlacementCapability;\n readonly binding: AssetBindingCapability;\n /** Present for producer-owned UI assets; absent for unrelated kinds. */\n readonly ui?: UiAuthoringCapability;\n readonly sourceOverrides?: readonly SourceOverrideDescriptor[];\n}\n\nconst UI_AUTHORING_CAPABILITY: UiAuthoringCapability = {\n contractVersion: '1',\n profileVersion: '1',\n preview: {\n operation: 'createUiPreviewSession',\n lifecycle: 'open-rebuild-retry-dispose',\n },\n mount: {\n operation: 'mountUi',\n lifecycle: 'mount-dispose',\n actionPort: 'onAction',\n },\n state: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n actions: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n reads: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n input: { status: 'supported', operation: 'dom-native', contractVersion: '1' },\n navigation: { status: 'supported', operation: 'dom-native', contractVersion: '1' },\n font: { status: 'supported', operation: 'ui-artifact-companion', contractVersion: '1' },\n localization: {\n status: 'unavailable',\n reason: {\n code: 'missing-producer-capability',\n hint: 'UI localization resources are not yet published through the UI authoring contract.',\n },\n },\n};\n\n/** Built-in defaults for legacy rows that do not carry an explicit override. */\nexport function authoringCapabilityForAssetKind(kind: string): AssetAuthoringCapability {\n switch (kind) {\n case 'ui':\n return {\n placement: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'UI assets mount through the UI runtime and are not ECS scene placements.',\n },\n },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'UI assets bind through their producer-owned UI runtime contract.',\n },\n },\n ui: UI_AUTHORING_CAPABILITY,\n };\n case 'scene':\n return {\n placement: { operation: 'addSceneAssetToScene' },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'Scene assets are placed as a scene mount.',\n },\n },\n };\n case 'mesh':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'MeshFilter',\n field: 'assetHandle',\n assetType: 'MeshAsset',\n cardinality: 'single',\n },\n requiredSlots: 1,\n },\n };\n case 'material':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'MeshRenderer',\n field: 'materials',\n assetType: 'MaterialAsset',\n cardinality: 'array',\n },\n requiredSlots: 1,\n },\n };\n case 'texture':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'createMaterialThenBindAssetRef',\n target: {\n component: 'MeshRenderer',\n field: 'materials',\n assetType: 'MaterialAsset',\n cardinality: 'array',\n },\n requiredSlots: 1,\n },\n };\n case 'particle-effect':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'ParticleEffectPlayer',\n field: 'effect',\n assetType: 'ParticleEffectAsset',\n cardinality: 'single',\n },\n requiredSlots: 1,\n },\n };\n default:\n return {\n placement: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: `No placement capability is published for asset kind '${kind}'.`,\n },\n },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: `No binding capability is published for asset kind '${kind}'.`,\n },\n },\n };\n }\n}\n\n/** Stable producer subject identity used by relations and diagnostics. */\nexport interface AssetSubjectRef {\n readonly type: AssetSubjectType;\n readonly id: string;\n}\n\n/** Provider identity carried with producer-owned facts; not a catalog locator. */\nexport interface ProviderProvenance {\n readonly provider: string;\n readonly version: string;\n readonly source?: string;\n}\n\n/** Producer-owned JSON payload for one stable imported-output source key. */\nexport type SourceOverridePayload = Readonly<Record<string, unknown>>;\n\n/** Optional Meta author facts keyed by the producer's stable sourceKey. */\nexport type SourceOverrideMap = Readonly<Record<string, SourceOverridePayload>>;\n\nexport interface SourceOverrideDescriptor {\n readonly sourceKey: string;\n /** Producer-owned semantic used by authoring hosts for catalog-aware validation. */\n readonly semantic?: 'mesh-material-slot-defaults';\n readonly payloadSchema?: unknown;\n}\n\n/** Shared payload contract explicitly published by Mesh-producing importers. */\nexport const MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA = {\n type: 'object',\n properties: {\n materialSlots: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n slotName: { type: 'string', minLength: 1 },\n sourceKey: { type: 'string', minLength: 1 },\n defaultMaterialGuid: { type: 'string' },\n },\n required: ['slotName'],\n additionalProperties: true,\n },\n },\n materialSlotDefaultOverrides: {\n type: 'object',\n additionalProperties: { type: 'string', nullable: true },\n },\n },\n additionalProperties: true,\n} as const;\n\nexport type SourceOverrideErrorCode =\n | 'unknown-source-key'\n | 'duplicate-source-key'\n | 'invalid-source-overrides'\n | 'invalid-source-override-payload';\n\nexport interface SourceOverrideDiagnostic {\n readonly code: SourceOverrideErrorCode;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n}\n\nexport type SourceOverrideValidationResult =\n | { readonly ok: true; readonly value: SourceOverrideMap | undefined }\n | { readonly ok: false; readonly error: SourceOverrideDiagnostic };\n\nfunction sourceOverrideError(\n code: SourceOverrideErrorCode,\n expected: string,\n hint: string,\n actual?: string,\n): SourceOverrideValidationResult {\n return {\n ok: false,\n error: { code, expected, hint, ...(actual === undefined ? {} : { actual }) },\n };\n}\n\nfunction isSourceOverridePayload(value: unknown): value is SourceOverridePayload {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Canonicalize legacy/empty override maps without changing producer payloads. */\nexport function canonicalizeSourceOverrides(value: unknown): SourceOverrideMap | undefined {\n if (value === undefined) return undefined;\n if (!isSourceOverridePayload(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0) return undefined;\n return Object.fromEntries(keys.sort().map((key) => [key, value[key]])) as SourceOverrideMap;\n}\n\nfunction validateSourceOverrideEntry(\n sourceKey: string,\n payload: unknown,\n declared: ReadonlySet<string>,\n seen: Set<string>,\n): SourceOverrideDiagnostic | undefined {\n if (seen.has(sourceKey)) {\n return {\n code: 'duplicate-source-key',\n expected: 'sourceKey values to be unique within sourceOverrides',\n hint: 'remove the duplicate source override',\n actual: sourceKey,\n };\n }\n seen.add(sourceKey);\n if (!declared.has(sourceKey)) {\n return {\n code: 'unknown-source-key',\n expected: 'sourceKey to be declared by the producer topology',\n hint: 'request a fresh Catalog topology before writing Meta',\n actual: sourceKey,\n };\n }\n if (!isSourceOverridePayload(payload)) {\n return {\n code: 'invalid-source-override-payload',\n expected: 'each source override payload to be a producer-owned object',\n hint: 'validate the payload with the producer schema',\n actual: sourceKey,\n };\n }\n return undefined;\n}\n\nfunction validateSourceOverrideEntries(\n entries: readonly (readonly [string, unknown])[],\n declared: ReadonlySet<string>,\n): SourceOverrideValidationResult {\n const seen = new Set<string>();\n for (const [sourceKey, payload] of entries) {\n const error = validateSourceOverrideEntry(sourceKey, payload, declared, seen);\n if (error !== undefined) return { ok: false, error };\n }\n return { ok: true, value: canonicalizeSourceOverrides(Object.fromEntries(entries)) };\n}\n\n/** Validate source override identity while leaving payload interpretation to the producer. */\nexport function validateSourceOverrideMap(\n value: unknown,\n declaredSourceKeys: readonly string[],\n): SourceOverrideValidationResult {\n const declared = new Set<string>();\n for (const sourceKey of declaredSourceKeys) {\n if (declared.has(sourceKey)) {\n return sourceOverrideError(\n 'duplicate-source-key',\n 'sourceKey values declared by a producer to be unique',\n 'repair the producer topology before publishing Meta',\n sourceKey,\n );\n }\n declared.add(sourceKey);\n }\n if (value === undefined) return { ok: true, value: undefined };\n if (Array.isArray(value)) {\n const entries: (readonly [string, unknown])[] = [];\n for (const item of value) {\n if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== 'string') {\n return sourceOverrideError(\n 'invalid-source-overrides',\n 'sourceOverrides to be an object keyed by sourceKey',\n 'pass a producer-owned source override map',\n );\n }\n entries.push([item[0], item[1]]);\n }\n return validateSourceOverrideEntries(entries, declared);\n }\n if (!isSourceOverridePayload(value)) {\n return sourceOverrideError(\n 'invalid-source-overrides',\n 'sourceOverrides to be an object keyed by sourceKey',\n 'pass a producer-owned source override map',\n );\n }\n return validateSourceOverrideEntries(Object.entries(value), declared);\n}\n\n/** Monotonic producer observation used to validate catalog continuity. */\nexport interface ResourceRevision {\n readonly digest: string;\n readonly observedAt: number;\n readonly rootId: string;\n}\n\nexport type AssetRelationType =\n | 'references'\n | 'reads'\n | 'depends-on'\n | 'owns'\n | 'contains'\n | 'produces'\n | 'materialized-as'\n | (string & {});\n\nexport interface AssetRelationPolicy {\n readonly ownership?: 'owned' | 'shared';\n readonly lifecycle?: 'authored' | 'derived';\n readonly strength?: 'required' | 'optional';\n}\n\n/** Structured graph edge emitted by a producer; consumers must preserve its fields. */\nexport interface AssetRelation {\n readonly from: AssetSubjectRef;\n readonly to: AssetSubjectRef;\n readonly type: AssetRelationType;\n readonly policy?: AssetRelationPolicy;\n readonly provenance: ProviderProvenance;\n}\n\nexport type CatalogDiagnosticSeverity = 'info' | 'warning' | 'blocking';\n\n/** Machine-readable catalog problem; consumers branch on fields, never message text. */\nexport interface CatalogDiagnostic {\n readonly code: string;\n readonly severity: CatalogDiagnosticSeverity;\n readonly message?: string;\n readonly subject?: AssetSubjectRef;\n readonly expected?: string;\n readonly actual?: string;\n readonly hint?: string;\n readonly authority?: 'producer' | 'pack' | 'catalog';\n readonly evidence?: readonly AssetSubjectRef[];\n readonly recoveryIntents?: readonly string[];\n}\n\n/** Closed set of contract failures returned by producer validation. */\nexport type ProducerContractErrorCode =\n | SourceOverrideErrorCode\n | TopologyConflictReason\n | 'invalid-source-key'\n | 'invalid-source-index'\n | 'invalid-producer-fact';\n\n/** Structured producer validation failure with its owning authority. */\nexport interface ProducerContractDiagnostic {\n readonly code: ProducerContractErrorCode;\n readonly subject: AssetSubjectRef;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n readonly authority: 'producer' | 'pack';\n}\n\n/** Result boundary for producer validation; success and failure are discriminated by `ok`. */\nexport type ProducerContractResult<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error: ProducerContractDiagnostic };\n\n/** Canonical producer output declaration used for topology matching and recovery. */\nexport interface ImportedOutputDeclaration {\n readonly guid: string;\n readonly sourceKey?: string;\n readonly sourceIndex: number;\n readonly kind: string;\n readonly name?: string;\n /** New kinds that may reuse this output's prior GUID. */\n readonly compatiblePreviousKinds?: readonly string[];\n}\n\nexport type ProposedOutput = ImportedOutputDeclaration;\n\nexport type ExistingOutput = ImportedOutputDeclaration;\n\nexport interface KindChange {\n readonly guid: string;\n readonly oldKind: string;\n readonly newKind: string;\n readonly sourceKey?: string;\n readonly action: 'remove-add' | 'preserve-guid';\n}\n\nexport type TopologyConflictReason =\n | 'duplicate-source-key'\n | 'missing-source-key'\n | 'source-index-ambiguous';\n\nexport interface MatchConflict {\n readonly reason: TopologyConflictReason;\n readonly sourceKey?: string;\n readonly previous: readonly ExistingOutput[];\n readonly next: readonly ProposedOutput[];\n}\n\nexport interface TopologyPreserved {\n readonly guid: string;\n readonly oldKey: string;\n readonly newKey: string;\n}\n\nexport interface TopologyDiff {\n readonly preserved: readonly TopologyPreserved[];\n readonly added: readonly ProposedOutput[];\n readonly removed: readonly ExistingOutput[];\n readonly changedKind: readonly KindChange[];\n readonly ambiguous: readonly MatchConflict[];\n}\n","import type { AssetStageErrorBase } from './asset-errors.js';\nimport type { SourceOverrideErrorCode, SourceOverrideMap } from './asset-producer.js';\nimport type { PackIndexEntry } from './catalog.js';\nimport type { Asset, AssetCodec, AssetRef, ImageError, TextureAsset } from './index.js';\n\nexport type ImportStageContractError = AssetStageErrorBase<'import', 'import-failed'>;\n\n// === Import contract SSOT (feat-20260603-asset-import-loader-injection M2 / w12) ===\n//\n// Decision anchors:\n// - requirements AC-07 (Importer dispatched by `meta.importer` string key) +\n// AC-08 (`importer-not-registered` fail-fast) + AC-09 (GUID import-stable\n// iron law: `guid-mismatch` / `import-produced-no-assets`) + AC-10\n// (`ImportErrorCode` is a closed union with exhaustive switch without default)\n// - plan-strategy D-6 (error model add-only: `ImportErrorCode` is an\n// independent closed union, not folded into `AssetErrorCode`) + D-1\n// (`ImporterRegistry` register/get/fail-fast mirrors LoaderRegistry) + D-4\n// (import runner skips the reserved `importer: 'shader'` key)\n// - research Finding 8 (`ImportTransport` interface slot; HTTP adapter is\n// OOS-2, landed in M4) + Finding 9 (`PackError` four-field shape is the\n// structural template)\n// - charter P3 (structured failure: `.code` / `.expected` / `.hint` /\n// `.detail`; AI users consume via property access, never `.message`\n// parsing) + P4 (consistent abstraction, structurally parallel to\n// `PackError` / `AssetError`)\n//\n// The import side is the build-time half of the import/load split. An\n// `Importer` turns an external source (a `.gltf` / `.png` / `.ttf` on disk)\n// plus its `*.meta.json` GUID declarations into in-memory `ImportedAsset[]`\n// (internal `Asset` PODs stamped with the meta-declared GUIDs). The import\n// runner then materializes those into the DDC (`.pack.json` / `.bin`). The\n// `Importer` itself stays pure of disk write + GUID minting — it consumes the\n// meta-declared GUIDs (GUID import-stable iron law) and emits PODs only.\n\n/**\n * Closed `ImportErrorCode` union for build-time import failures.\n * Used exclusively by the build-time `@forgeax/engine-import` runner +\n * `ImporterRegistry` fail-fast chain. Domain-separated from the runtime\n * `AssetErrorCode` (the typed `AssetRegistry.load` surface) and the disk-scanner\n * `PackErrorCode` — disjoint lifecycle phases. Counts evolve; see\n * AGENTS.md §Error model for the live roster.\n *\n * Exhaustive `switch (err.code)` needs no `default:` — TypeScript guards\n * union completeness at compile time (charter P2 machine-readable union >\n * prose + P3 explicit failure).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'importer-not-registered'` | the import runner read `meta.importer` but the injected `ImporterRegistry` has no importer for that key; `.detail.importer` is the missing key and `.detail.registeredImporters` lists the keys currently wired (charter P3 — AI users read `.detail.registeredImporters` to know what to inject). |\n * | `'source-read-failed'` | the source file referenced by `meta.source` could not be read (missing / unreadable); `.detail.source` is the path and `.detail.reason` the underlying error string. |\n * | `'import-produced-no-assets'` | the importer returned an empty `ImportedAsset[]`, or omitted a GUID that `meta.subAssets[]` declared (the produced GUID set is not a superset of the declared set); `.detail.missingGuids` lists the declared GUIDs the importer failed to produce. |\n * | `'guid-mismatch'` | the importer produced a GUID that `meta.subAssets[]` never declared (violates the GUID import-stable iron law); `.detail.unexpectedGuids` lists the produced GUIDs absent from the declared set. |\n * | `'import-internal-error'` | the importer failed at runtime. Two sub-cases ride `.detail`: a build-time module-LOAD failure surfaces `.detail.loadError`, while a conversion THROW surfaces `.detail.reason`. |\n * | `'source-validation-failed'` | the source was readable but failed an authoring rule; `.detail.diagnostics` contains source-located, machine-readable findings. |\n */\nexport type ImportErrorCode =\n | SourceOverrideErrorCode\n | 'importer-not-registered'\n | 'source-read-failed'\n | 'import-produced-no-assets'\n | 'guid-mismatch'\n | 'mesh-material-slot-topology-change'\n | 'import-internal-error'\n | 'source-validation-failed';\n\n/** A source range shared by all import diagnostics. */\nexport interface ImportSourceRange {\n readonly start: number;\n readonly end: number;\n readonly line: number;\n readonly column: number;\n}\n\n/** A related source location for a cross-file import diagnostic. */\nexport interface ImportDiagnosticLocation {\n readonly sourcePath: string;\n readonly sourceRange: ImportSourceRange;\n}\n\n/** Machine-readable provenance for one blocking or quality import finding. */\nexport interface ImportDiagnostic {\n readonly code: string;\n readonly severity: 'error' | 'warning';\n readonly sourcePath: string;\n readonly sourceRange: ImportSourceRange;\n readonly rule: string;\n readonly expected: string;\n readonly actual: string;\n readonly hint: string;\n readonly relatedLocations?: readonly ImportDiagnosticLocation[];\n}\n\n/**\n * Discriminated detail union for {@link ImportError} — narrowed per\n * `ImportError.code`. AI users access `err.detail.<field>` directly after\n * `switch (err.code)` narrows the variant. Structurally parallel to\n * `PackErrorDetail` (the `code` field is intentionally absent from each\n * variant; identify via the top-level `ImportError.code`).\n */\nexport type ImportErrorDetail =\n | {\n /** The `meta.importer` key with no registered importer. */\n readonly importer: string;\n /** The importer keys currently wired into the registry (insertion order). */\n readonly registeredImporters: readonly string[];\n }\n | {\n /** The `meta.source` path that could not be read. */\n readonly source: string;\n /** The underlying read error message. */\n readonly reason: string;\n }\n | {\n /** Declared sub-asset GUIDs the importer failed to produce (empty when the importer produced nothing at all). */\n readonly missingGuids: readonly string[];\n }\n | {\n /** Produced GUIDs absent from the `meta.subAssets[]` declared set. */\n readonly unexpectedGuids: readonly string[];\n }\n | {\n /** The original thrown error message (importer loaded but its conversion threw). */\n readonly reason: string;\n }\n | {\n /**\n * The module-load failure message (feat-20260629 D-5): the importer\n * module / native addon could not be loaded at build time (e.g.\n * module-not-found, native-addon-not-built). Distinguishes a LOAD\n * failure from a conversion THROW (`reason`) under the same\n * `import-internal-error` code without growing the closed ImportErrorCode\n * union. AI users branch on `'loadError' in err.detail`.\n */\n readonly loadError: string;\n }\n | {\n /** Source-located authoring findings retained across the import boundary. */\n readonly diagnostics: readonly ImportDiagnostic[];\n }\n | {\n /** Source override identity that failed Meta topology validation. */\n readonly sourceKey?: string;\n readonly declaredSourceKeys: readonly string[];\n readonly reason?: string;\n }\n | {\n readonly meshGuid: string;\n readonly meshSourceKey?: string;\n readonly previousIndices: readonly number[];\n readonly nextIndices: readonly number[];\n };\n\n/**\n * Structured import error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `PackError` / `AssetError`\n * (charter P4 consistent abstraction). `.detail` is narrowed per `.code` via\n * {@link ImportErrorDetail}.\n *\n * AI users consume the structured surface via property access:\n * `switch (err.code) { case 'guid-mismatch': ... err.detail.unexpectedGuids ... }`\n * — never by parsing `.message` (charter P3 red line).\n */\nexport class ImportError extends Error {\n readonly code: ImportErrorCode;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n readonly detail: ImportErrorDetail;\n\n constructor(args: {\n code: ImportErrorCode;\n expected: string;\n actual?: string;\n hint: string;\n detail: ImportErrorDetail;\n }) {\n super(`[ImportError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'ImportError';\n this.code = args.code;\n this.expected = args.expected;\n if (args.actual !== undefined) this.actual = args.actual;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT. `Record<ImportErrorCode, string>`\n * makes a new closed-union member a compile-time error here as well\n * (reinforces charter P3 explicit failure). Consumed by the import runner +\n * tests so the producer and the fixtures share one source of truth.\n */\nexport const IMPORT_ERROR_HINTS: Readonly<Record<ImportErrorCode, string>> = {\n 'importer-not-registered':\n 'no importer registered for this meta.importer key; register one via importers.register(importer) (the importer carries its own key, e.g. gltfImporter / imageImporter); err.detail.registeredImporters lists the keys currently wired',\n 'source-read-failed':\n 'the file at meta.source could not be read; check the path is correct relative to the sidecar and the process has read access',\n 'import-produced-no-assets':\n 'the importer produced no assets, or omitted a GUID that meta.subAssets[] declared; the produced GUID set must be a superset of the declared set (GUID import-stable iron law); err.detail.missingGuids lists the declared GUIDs not produced',\n 'guid-mismatch':\n 'the importer produced a GUID that meta.subAssets[] never declared (violates the GUID import-stable iron law: GUIDs come from the external meta, never minted by the importer); err.detail.unexpectedGuids lists the offending GUIDs',\n 'mesh-material-slot-topology-change':\n 'the importer could not match previous and current Mesh material slots without ambiguity; name source materials uniquely or repair their stable sourceKey values before reimport',\n 'import-internal-error':\n 'the importer failed at runtime; branch on err.detail: a conversion THROW carries err.detail.reason (the loaded importer threw while converting the source — an importer bug, not a meta / source problem), while a build-time module-LOAD failure carries err.detail.loadError (the host importer module / native addon could not be imported)',\n 'source-validation-failed':\n 'the source violates an import authoring rule; inspect err.detail.diagnostics fields (code, sourcePath, sourceRange, rule, expected, actual, hint, and relatedLocations) and fix the referenced source',\n 'unknown-source-key':\n 'sourceOverrides contains a key absent from meta.subAssets[]; refresh the producer topology and use one of err.detail.declaredSourceKeys',\n 'duplicate-source-key':\n 'the producer declared the same sourceKey more than once; repair the Meta topology before importing',\n 'invalid-source-overrides':\n 'sourceOverrides must be an object keyed by producer-owned sourceKey values',\n 'invalid-source-override-payload':\n 'each sourceOverrides value must be a producer-owned object validated by the importer',\n};\n\n/**\n * One asset produced by an {@link Importer}: the meta-declared `guid`, the\n * in-memory `Asset` POD, and its outbound GUID cross-references (`refs`). The\n * import runner folds these into the DDC `.pack.json` `assets[]` rows (one\n * `ImportedAsset` -> one `{ guid, kind, payload, refs }` row).\n *\n * The `guid` always comes from `meta.subAssets[].guid` (GUID import-stable\n * iron law) — the importer never mints it; it reads the declared GUID off the\n * meta and stamps it here. `kind` mirrors the `Asset.kind` discriminant so the\n * DDC row and the runtime loader dispatch on the same string.\n */\nexport interface ImportedAsset<P = Asset> {\n readonly guid: string;\n readonly kind: string;\n readonly name?: string;\n readonly payload: P;\n readonly refs: readonly AssetRef[];\n readonly artifacts: Readonly<Record<string, ImportedArtifactBody>>;\n}\n\n/**\n * One declared sub-asset entry the import runner hands to an\n * {@link Importer.import} call — the meta-declared `guid` + its `sourceIndex`\n * + `kind`. Mirrors the `meta.subAssets[]` rows so the importer can map a\n * source object index to the GUID it must stamp (GUID import-stable iron law).\n */\nexport interface ImportSubAsset {\n readonly guid: string;\n readonly sourceIndex: number;\n /** Producer-owned semantic identity used to look up sourceOverrides. */\n readonly sourceKey?: string;\n readonly kind: string;\n}\n\n/**\n * Capabilities + declarations the import runner wires into an\n * {@link Importer.import} call. The importer reads the source bytes via\n * `readSource`, the GUID declarations via `subAssets`, and the free-form\n * importer settings via `importSettings`. It stays pure of disk write +\n * registry bookkeeping (pipeline isolation, architecture-principles #4).\n *\n * - `source` — the `meta.source` path (relative to the sidecar), for\n * diagnostics + the importer's own external-resource resolution base.\n * - `readSource()` — fetch the raw source bytes (the runner has already\n * resolved the path); a structured failure here surfaces as\n * `source-read-failed`.\n * - `subAssets` — the `meta.subAssets[]` GUID declarations the importer must\n * honour (GUID import-stable iron law).\n * - `importSettings` — free-form importer settings copied verbatim from the\n * sidecar.\n * - `readSibling(uri)` — fetch raw bytes of a file co-located with the\n * primary source (e.g. an `.gltf` referencing an external `.bin` /\n * `.png` via relative URI). Failures surface as `source-read-failed`\n * (C-6 — no specialised error code; the URI is forensic detail). Used\n * by gltfImporter to resolve `images[].uri` external references at\n * import time.\n * - `decodeImage(bytes, mimeType, importSettings)` — decode raw image\n * bytes (PNG / JPEG) into a `TextureAsset` POD plus a `bytes` copy\n * suitable for `<guid>.bin` emission. When the host applies an offline\n * delivery codec, it also returns the artifact media type and codec so\n * gltfImporter can preserve those facts in the Pack v2 envelope. The seam\n * keeps gltfImporter\n * out of `@forgeax/engine-image` (D-1: zero static `from\n * '@forgeax/engine-image'` edge in `packages/gltf/src`). The\n * concrete implementation (parseImage + format derivation) lives\n * behind `@forgeax/engine-image/image-importer`; the build-time\n * orchestrator (vite-plugin-pack / cli-gltf / tests) binds the\n * callback when constructing the runner's `ImportRunnerFs`.\n */\nexport interface ImportContext {\n readonly source: string;\n readSource(): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: unknown }\n >;\n readSibling(\n uri: string,\n ): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: ImportError }\n >;\n decodeImage(\n bytes: Uint8Array,\n mimeType: 'image/png' | 'image/jpeg' | 'image/x-tga',\n importSettings: Readonly<Record<string, unknown>>,\n ): Promise<\n | {\n readonly ok: true;\n readonly value: {\n readonly texture: TextureAsset;\n readonly bytes: Uint8Array;\n readonly mediaType?: string;\n readonly assetCodec?: AssetCodec;\n };\n }\n | { readonly ok: false; readonly error: ImageError }\n >;\n readonly subAssets: readonly ImportSubAsset[];\n readonly importSettings: Readonly<Record<string, unknown>>;\n /** Optional Meta author facts passed through without importer-kind branching. */\n readonly sourceOverrides?: SourceOverrideMap;\n}\n\n/**\n * Build-time importer injected into the `ImporterRegistry`. One importer per\n * `meta.importer` key; the import runner dispatches on the key.\n *\n * `import` is pure of disk write + GUID minting: it reads the source via\n * `ctx.readSource()`, honours the `ctx.subAssets[]` GUID declarations, and\n * returns the produced `ImportedAsset[]`. The runner validates the produced\n * GUID set against the declared set (GUID import-stable iron law) and writes\n * the DDC. A thrown error is wrapped by the runner into\n * `import-internal-error` (charter P3) — importers may throw, but should\n * prefer returning a partial / empty result so the runner can attribute the\n * failure precisely.\n */\nexport interface Importer {\n readonly key: string;\n // biome-ignore lint/suspicious/noExplicitAny: pending downstream importer migration keeps old consumers source-compatible\n import(ctx: ImportContext): Promise<any> | any;\n}\n/**\n * Interface slot for the M4 lazy-import transport (OOS-2). A runtime\n * `ImportTransport` fetches a missing DDC artefact on demand (the shipped form\n * never falls back to a runtime import; see `AssetErrorCode 'asset-not-imported'`).\n * Declared here as a contract seam only — the HTTP adapter lands in M4 w31;\n * M2 does not implement it (plan-strategy D-6 / research Finding 8).\n */\nexport interface ImportTransport {\n /**\n * Trigger an on-demand DDC import for a GUID at runtime. On success the\n * transport returns the freshly imported catalog rows for the GUID (and any\n * sub-asset siblings produced by the same import) so the caller patches just\n * those rows into its catalog cache -- per-asset incremental, never a\n * whole-catalog re-fetch (the four-verb redesign, 2026-06-06). `entries` may\n * be empty when the transport imported the artefact but does not surface the\n * rows; the caller then re-resolves the GUID from its (possibly stale) cache.\n * `ok: false` means the import did not produce an artefact and the caller\n * surfaces `asset-not-imported`.\n */\n fetchPack(\n guid: string,\n scope?: Pick<\n import('./runtime-scope.js').RuntimeAssetBinding,\n 'scopeId' | 'generation' | 'status'\n >,\n ): Promise<\n { readonly ok: true; readonly entries?: readonly PackIndexEntry[] } | { readonly ok: false }\n >;\n}\n\n/** A normalized filesystem path observed by an importer read attempt. */\nexport type SourceDependency = string;\n\n/** Logical artifact content emitted by one imported asset. */\nexport interface ImportedArtifactBody {\n readonly mediaType: string;\n readonly assetCodec?: AssetCodec;\n readonly bytes: Uint8Array;\n}\n\n/** Generic import output shared by every importer and transport. */\nexport interface ImportProduct<P = Asset> {\n readonly assets: readonly ImportedAsset<P>[];\n readonly sourceDependencies: readonly SourceDependency[];\n}\n\n/** Structured result returned by an importer. */\nexport type ImportResult<P = Asset> =\n | { readonly ok: true; readonly value: ImportProduct<P> }\n | { readonly ok: false; readonly error: ImportError };\n\n/* ImportTransport is intentionally kept with the build-time contract. */\n","// @forgeax/engine-types — POD types, union aliases, and cross-package primitives SSOT.\n//\n// Proposition: this package is the single source of truth for shared shapes that\n// must NOT diverge across @forgeax/engine-rhi / ecs / naga / image / gltf / console /\n// render-graph / shader / future renderer packages.\n//\n// Scope:\n// - POD types & union aliases — Asset / MaterialAsset / RenderQueue / PassKind /\n// FontAsset / RenderPipelineAsset / SceneAsset /\n// PackErrorCode / ImageErrorCode / AudioErrorCode / PhysicsErrorCode etc.\n// - Structured error classes — AssetError / FontError / TextError / AudioError /\n// PhysicsError (carry .code / .expected / .hint surface).\n// - Project-wide Result<T, E> + ok / err factories (tweak-20260612-result-into-types\n// consolidated 5 byte-aligned-by-prose copies into this single module).\n// - GPUFlagsConstant aliases for @webgpu/types numeric runtime constants — the\n// global objects (e.g. GPUBufferUsage.MAP_READ) are still consumed directly by\n// upstream callers; only the literal type aliases live here (decision S-6 /\n// research F-2 option (b)).\n//\n// Shape rules:\n// - math-free (no vec / mat / quat dependency).\n// - Single-source policy — fields already exported by @webgpu/types are re-exported\n// verbatim as one-line aliases; never duplicated here.\n//\n// Anchors: requirements §AC AC-01 + MVP-1.5; plan-strategy §2 S-6 + §7.6 propositions\n// 4 / 5; research §F-2 (`GPUFlagsConstant = number` alias).\n\n/// <reference types=\"@webgpu/types\" />\n\n// === Handle SSOT barrel re-export (feat-20260517-handle-type-unify M2 / D-2) ====\n//\n// `./handle` carries the unique double-axis Handle<T extends string, M> brand\n// + AssetTagMap + TagOf + 3 factories (toUnique / toShared / unwrapHandle).\n// The legacy 1-arg form that lived here at M1 has been physically deleted\n// (M2 t10) so the package surface exposes a single Handle shape (charter F1\n// single-entry indexability; AC-03 grep gate).\n\nimport type { AnimationTargetIdValue } from './animation-target';\nimport type { AudioClipAsset } from './asset.js';\nimport type { MaterialAsset } from './material/asset.js';\nimport type { ParticleEffectAsset } from './vfx';\n\nexport type { AnimationTargetIdValue } from './animation-target';\nexport * from './asset.js';\nexport * from './asset-errors.js';\nexport type {\n AssetArtifactReader,\n AssetDecoder,\n AssetDecoderContribution,\n AssetDecoderContributionRef,\n AssetDecoderInput,\n AssetDecoderLease,\n AssetDecoderResult,\n AssetKind,\n AssetKindPayload,\n AssetRegistryAction,\n AssetRuntimeApiGroups,\n BuiltinAssetKind,\n BuiltinAssetKindToken,\n BuiltinAssetPayload,\n} from './asset-runtime.js';\nexport * from './handle';\nexport * from './material/index.js';\n\n// === Result<T, E> SSOT (tweak-20260612-result-into-types) ======================\n//\n// Single physical source of `Result<T, E>` + `ok(...)` / `err(...)`. Replaces\n// the prior dual copies in packages/rhi/src/errors.ts + packages/ecs/src/result.ts\n// (those copies were \"byte-for-byte aligned\" by prose, not by mechanism).\n// AI users import the binary success/failure carrier via `@forgeax/engine-types`\n// (or via the rhi/ecs package barrels which re-export this same module).\nexport * from './result';\nexport * from './runtime-scope';\nexport * from './vfx';\n\n// === Sub-asset POD SSOT (feat-20260615-fbx-importer-via-sdk M1 / t9) ===========\n//\n// Importer-independent pure-data IR types shared across glTF / FBX / future\n// format importers. These are pre-kind, pre-guid data carriers — each importer\n// writes these Pods from its format-specific JSON POD, and `to-asset-pack`\n// promotes them to registry-ready Asset handles.\n//\n// Design axioms:\n// - SSOT (architecture-principles #1): defined once in @forgeax/engine-types,\n// consumed via import by gltf / fbx / future importer packages.\n// - Derive, don't duplicate (#2): gltf/fbx drop their local MeshIr/MeshRecord\n// and import MeshPod — no per-package copy.\n// - No format prefix (plan-strategy section 8): Pod types are named after the\n// *asset kind* they represent, not the source format. AI users write code\n// that reads MeshPod regardless of whether the source was FBX or glTF.\n// - Pre-kind data (charter P4): Pods carry raw geometric/material data without\n// `kind` discriminant fields. The importer bridge layer adds `kind` when\n// converting Pod -> Asset handle.\n//\n// Pod roster (AC-01..AC-07):\n// MeshPod — vertices/indices/attributes/submeshes\n// MaterialPod — PBR parameter values (baseColor/metallic/roughness)\n// ScenePod — entity hierarchy + mount points\n// TexturePod — external file path (cross-platform normalized)\n// SkeletonPod — joint count + inverse bind matrices\n// SkinPod — skeleton reference + joint paths\n// AnimationClipPod — duration + channels + samplers\n\n// === AC-01: MeshPod — pure geometric data ===\n\n/** Per-submesh descriptor within a MeshPod. */\nexport interface MeshSubmeshPod {\n /** Vertex count for this submesh (draw count when non-indexed). */\n readonly vertexCount: number;\n /** Index count when indexed; 0 for non-indexed geometry. */\n readonly indexCount: number;\n /** Byte offset into the shared indices buffer (0-based). */\n readonly indexOffset: number;\n /** Material binding index into the parent document's materials array. */\n readonly materialIndex: number | null;\n /** Primitive topology. */\n readonly topology: 'triangle-list' | 'line-list' | 'line-strip' | 'point-list';\n}\n\n/** MeshPod: pre-kind geometric data IR shared across importers. */\nexport interface MeshPod {\n /** Optional debug name from source document. */\n readonly name?: string;\n /** Packed vertex positions (Float32Array, 3 floats per vertex). */\n readonly vertices: Float32Array;\n /** Packed triangle indices (Uint16Array or Uint32Array). Absent when non-indexed. */\n readonly indices?: Uint16Array | Uint32Array;\n /** Per-vertex attributes keyed by semantic (POSITION/NORMAL/TEXCOORD_0/JOINTS_0/WEIGHTS_0). */\n readonly attributes: Record<string, Float32Array | Uint16Array | Uint32Array>;\n /** Optional additive morph deltas, one entry per target. */\n readonly morphTargets?: readonly MorphTarget[];\n /** Optional default morph weights, one value per target. */\n readonly morphWeights?: Float32Array;\n /** Per-submesh descriptors (>=1). */\n readonly submeshes: readonly MeshSubmeshPod[];\n /** Source mesh index within the original document (for diagnostic mapping). */\n readonly sourceIndex: number;\n}\n\n// === AC-02: MaterialPod — PBR parameter values ===\n\n/** MaterialPod: pre-kind PBR material data IR shared across importers. */\nexport const MATERIAL_TEXTURE_SLOTS = [\n 'baseColorTexture',\n 'normalTexture',\n 'specularTintTexture',\n 'metallicRoughnessTexture',\n 'emissiveTexture',\n 'occlusionTexture',\n] as const;\n\nexport type MaterialTextureSlot = (typeof MATERIAL_TEXTURE_SLOTS)[number];\n\nexport interface MaterialTextureBindingPod {\n /** Standard material value slot receiving the texture reference. */\n readonly slot: MaterialTextureSlot;\n /** Index into the parent document's textures array. */\n readonly textureIndex: number;\n /** Optional source UV set. */\n readonly texCoord?: number;\n}\n\nexport interface MaterialPod {\n /** Optional debug name from source document. */\n readonly name?: string;\n /** RGBA base color factor (linear space). */\n readonly baseColorFactor: readonly [number, number, number, number];\n /** Metallic factor (0..1). */\n readonly metallicFactor: number;\n /** Roughness factor (0..1). */\n readonly roughnessFactor: number;\n /** Index into the parent document's textures array for base color map. */\n readonly baseColorTextureIndex?: number;\n /** Index for metallic-roughness packed texture. */\n readonly metallicRoughnessTextureIndex?: number;\n /** Index for normal map. */\n readonly normalTextureIndex?: number;\n /** Index for occlusion map. */\n readonly occlusionTextureIndex?: number;\n /** Index for emissive map. */\n readonly emissiveTextureIndex?: number;\n /** Index for a specular tint map. */\n readonly specularTintTextureIndex?: number;\n /** Engine-owned semantic texture bindings from the producer. */\n readonly textureBindings?: readonly MaterialTextureBindingPod[];\n}\n\n// === AC-03: ScenePod — entity hierarchy ===\n\n/** A single entity node within a ScenePod hierarchy. */\nexport interface SceneEntityPod {\n /** Entity name (for Name component attachment). */\n readonly name: string;\n /** Decomposed local transform (TRS). */\n readonly transform: {\n readonly translation: readonly [number, number, number];\n readonly rotation: readonly [number, number, number, number];\n readonly scale: readonly [number, number, number];\n };\n /** Index into the parent document's meshes array. Null when not a mesh node. */\n readonly meshIndex: number | null;\n /** Children entity indices in the flattened entities array. */\n readonly children: readonly number[];\n}\n\n/** ScenePod: entity hierarchy IR shared across importers. */\nexport interface ScenePod {\n /** Optional scene name. */\n readonly name?: string;\n /** Flattened entity list (topological order, parents before children). */\n readonly entities: readonly SceneEntityPod[];\n /** Index of the default/root scene entity. */\n readonly rootEntityIndex: number;\n}\n\n// === AC-04: TexturePod — external file path ===\n\n/** TexturePod: external texture reference IR shared across importers. */\nexport interface TexturePod {\n /** Optional texture name. */\n readonly name?: string;\n /** Filesystem path relative to the source document, with '/' separators. */\n readonly filePath: string;\n /** The producer-declared relative path before host resolution. */\n readonly relativeFilePath?: string;\n /** Parse-scope absolute hint; never persist this into project metadata. */\n readonly absoluteFilePath?: string;\n /** Embedded encoded image bytes, when the FBX contains the texture payload. */\n readonly embeddedBytes?: Uint8Array;\n /** Producer texture kind; only file/embedded textures are importable. */\n readonly type?: 'file' | 'layered' | 'procedural' | 'shader' | 'unknown';\n /** Source texture index within the original document. */\n readonly sourceIndex: number;\n}\n\n// === AC-05: SkeletonPod — joint hierarchy ===\n\n/** SkeletonPod: skeleton joint data IR shared across importers. */\nexport interface SkeletonPod {\n /** Number of joints. */\n readonly jointCount: number;\n /** Inverse bind matrices, Float32Array of length jointCount * 16. */\n readonly inverseBindMatrices: Float32Array;\n /** Per-joint name path from scene root (parallel to joints array). */\n readonly jointPaths: readonly string[];\n}\n\n// === AC-06: SkinPod — vertex skinning data ===\n\n/** Per-vertex joint influence descriptor. */\nexport interface SkinVertexInfluencePod {\n /** 4 joint indices (Uint16Array), always padded to 4 entries. */\n readonly jointIndices: Uint16Array;\n /** 4 joint weights (Float32Array), always padded to 4 entries. */\n readonly jointWeights: Float32Array;\n}\n\n/** SkinPod: vertex skinning data IR shared across importers. */\nexport interface SkinPod {\n /** GUID-like identifier for the associated SkeletonAsset (resolved at bridge time). */\n readonly skeletonGuid: string;\n /** Joint name paths (same as SkeletonPod.jointPaths for cross-reference). */\n readonly jointPaths: readonly string[];\n /** Number of influenced vertices. */\n readonly vertexCount: number;\n /** Per-vertex joint influences (4 joints per vertex). */\n readonly influences: readonly SkinVertexInfluencePod[];\n}\n\n// === AC-07: AnimationClipPod — keyframe animation ===\n\n/** Animation sampler (keyframe data for one property). */\nexport interface AnimationSamplerPod {\n /** Keyframe timestamps (ascending, seconds). */\n readonly input: Float32Array;\n /** Keyframe values (packed per-element stride). */\n readonly output: Float32Array;\n /** Interpolation mode. */\n readonly interpolation: 'LINEAR' | 'STEP';\n}\n\n/** Animation channel (one target-property pair). */\nexport interface AnimationChannelPod {\n /** Stable animation target identity. */\n readonly targetId: AnimationTargetIdValue;\n /** Target property: 'translation' | 'rotation' | 'scale' | 'weights'. */\n readonly property: 'translation' | 'rotation' | 'scale' | 'weights';\n /** Sampler driving this channel. */\n readonly sampler: AnimationSamplerPod;\n}\n\n/** AnimationClipPod: keyframe animation clip IR shared across importers. */\nexport interface AnimationClipPod {\n /** Optional clip name. */\n readonly name?: string;\n /** Clip duration in seconds (max sampler.input[last]). */\n readonly duration: number;\n /** Per-animation-target-property channels. */\n readonly channels: readonly AnimationChannelPod[];\n}\n\n// === Asset system v1 SSOT (feat-20260511-asset-system-v1) ======================\n//\n// Decision anchors:\n// - requirements §G7 + §2 row 8 + AC-09 / AC-15 / AC-21 (4-variant Asset\n// discriminated union, 14-key VertexAttributeMap closed set,\n// AssetErrorCode 4-member closed union elevated to TS alias)\n// - plan-strategy §2 D-P1 (@forgeax/engine-types single-file SSOT for\n// Asset union + AssetErrorCode; 4-member AssetErrorCode independent from\n// RhiErrorCode, AI users discover through one-line import)\n// - plan-strategy §7.2 (lowercase key alignment with Three.js r184\n// BufferGeometry mental migration; D-P5 preserves segments 6 params)\n// - plan-strategy §7.3 (AssetError .hint strings per error code, verbatim)\n// - charter proposition 1 (single-entry IDE autocomplete via\n// `@forgeax/engine-types`) + proposition 3 (machine-readable union >\n// prose) + proposition 4 (explicit failure - exhaustive switch needs no\n// default fallback) + proposition 5 (consistent abstraction - structurally\n// parallel to RhiError / InspectorError / MetricError 4-field surface)\n// - architecture-principles #1 SSOT (4 literals + shape live here once;\n// @forgeax/engine-runtime AssetRegistry / tests / AGENTS.md Error model\n// table all reference this module)\n\n/**\n * Mesh asset POD shape aligned with Three.js r184 BufferGeometry mental\n * model (plan-strategy §7.2 naming convention). `vertices` is the interleaved\n * or primary position buffer; `indices` narrows to `Uint16Array | Uint32Array`\n * per WebGPU spec index format; `attributes` is the VertexAttributeMap\n * lowercase-key closed set.\n *\n * Canonical lowercase keys include position, normal, uv, tangent, skinIndex,\n * skinWeight, uv1..uv7 and optional linear RGBA color.\n *\n * Designed for M3 GLTF loader single-layer mapping\n * (`POSITION -> position` / `TEXCOORD_0 -> uv` etc.) without runtime rename.\n */\nexport interface MeshAsset {\n readonly kind: 'mesh';\n readonly vertices: Float32Array;\n /**\n * Index buffer. Optional: vertex-only meshes (point-list / line-list with no\n * shared vertices) omit it, and the engine takes a non-indexed draw path\n * (`pass.draw(vertexCount)` instead of `pass.drawIndexed`). When present the\n * indexed path is byte-for-byte unchanged.\n */\n readonly indices?: Uint16Array | Uint32Array;\n readonly attributes: VertexAttributeMap;\n /**\n * Axis-aligned bounding box in local space: 6 floats [minX, minY, minZ, maxX, maxY, maxZ].\n *\n * Producer obligation: every MeshAsset MUST carry a computed `aabb` from its\n * position attribute. Built-in producers (glTF, FBX, geometry factories)\n * fill it automatically via `box3.fromPositions` -- the single\n * authoritative implementation in @forgeax/engine-math.\n *\n * When `aabb` is `undefined`, the pick() broad-phase and frustum culling\n * silently skip the mesh -- pick() returns `undefined` for every ray query\n * against it with no diagnostic signal. AI users hand-writing MeshAsset\n * must self-check that `aabb` is populated.\n *\n * Empty / degenerate position input (0 vertices) produces an\n * inverted-infinity empty box (min = +Infinity, max = -Infinity).\n * Consumers reject this for picking (min.x > max.x) and may skip it\n * for culling.\n *\n * The bare Float32Array keeps engine-types math-free (no Box3 branded\n * type dependency). Consumers narrow to the math-layer Box3 via\n * `as Box3Like`.\n */\n readonly aabb?: Float32Array;\n /**\n * Submeshes partition the index/vertex range into independent draw calls,\n * each with its own topology (one of the 5 WebGPU primitives:\n * 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip').\n *\n * Every mesh must declare at least one submesh. The engine draws one\n * `drawIndexed` (or `draw` for vertex-only) per submesh entry, and pairs\n * them with `MeshRenderer.materials[]` by index position.\n *\n * Must be non-empty: an empty array triggers a `mesh-asset-submeshes-empty`\n * AssetError at register-time (fail-fast, charter P3 explicit failure).\n */\n readonly submeshes: readonly Submesh[];\n /** Stable, mesh-owned material entry points shared by every instance. */\n readonly materialSlots: readonly MeshMaterialSlot[];\n /** Target-major dense morph deltas. */\n readonly morphTargets?: readonly MorphTarget[];\n /** Authored default weights, one value per morph target when present. */\n readonly morphWeights?: Float32Array;\n}\n\nexport interface MorphTarget {\n readonly position?: Float32Array;\n readonly normal?: Float32Array;\n readonly tangent?: Float32Array;\n}\n\n/** Mesh-owned default binding for one stable material slot. */\nexport interface MeshMaterialSlot {\n /** Unique, non-empty display/tooling name within the mesh. */\n readonly slotName: string;\n /** Producer-owned stable identity used to preserve slot indices on reimport. */\n readonly sourceKey?: string;\n /** Missing means the slot intentionally inherits the neutral engine material. */\n readonly defaultMaterial?: AssetGuid;\n}\n\n/** JSON-safe producer topology persisted beside an imported Mesh output. */\nexport interface MeshMaterialSlotTopologyEntry {\n readonly slotName: string;\n readonly sourceKey?: string;\n readonly defaultMaterialGuid?: string;\n /** Persisted removed-slot identity; never participates in cooked bindings. */\n readonly tombstone?: true;\n}\n\n/** Resolve the persisted source/authoring layers to the one runtime Mesh default. */\nexport function resolveMeshMaterialSlotDefaultGuid(\n slot: MeshMaterialSlotTopologyEntry,\n authoredDefaultMaterialGuid?: string | null,\n): string | undefined {\n if (authoredDefaultMaterialGuid !== undefined) {\n return authoredDefaultMaterialGuid === null ? undefined : authoredDefaultMaterialGuid;\n }\n return slot.defaultMaterialGuid;\n}\n\nexport interface MeshMaterialSlotTopologyChange {\n readonly code: 'mesh-material-slot-topology-change';\n readonly previousIndices: readonly number[];\n readonly nextIndices: readonly number[];\n readonly hint: string;\n}\n\nexport type MeshMaterialSlotReconcileResult =\n | {\n readonly ok: true;\n readonly slots: readonly MeshMaterialSlotTopologyEntry[];\n /** New source-order slot index -> stable persisted slot index. */\n readonly currentToStableSlot: readonly number[];\n }\n | { readonly ok: false; readonly error: MeshMaterialSlotTopologyChange };\n\n/**\n * Preserve positional renderer overrides across source reimport.\n *\n * Stable sourceKey wins, then a unique slotName. A single remaining pair is\n * the only unambiguous source-order fallback. Removed slots remain as\n * defaultless tombstones; new slots append, so an old index never silently\n * starts naming a different source material.\n */\nexport function reconcileMeshMaterialSlotTopology(\n current: readonly MeshMaterialSlotTopologyEntry[],\n previous: readonly MeshMaterialSlotTopologyEntry[] = [],\n): MeshMaterialSlotReconcileResult {\n if (previous.length === 0) {\n return { ok: true, slots: [...current], currentToStableSlot: current.map((_, index) => index) };\n }\n\n const stable: MeshMaterialSlotTopologyEntry[] = previous.map((slot) => ({\n slotName: slot.slotName,\n ...(slot.sourceKey === undefined ? {} : { sourceKey: slot.sourceKey }),\n tombstone: true,\n }));\n const mapping = new Array<number>(current.length).fill(-1);\n const usedPrevious = new Set<number>();\n\n const uniqueIndex = (\n slots: readonly MeshMaterialSlotTopologyEntry[],\n read: (slot: MeshMaterialSlotTopologyEntry) => string | undefined,\n ): Map<string, number> => {\n const first = new Map<string, number>();\n const duplicates = new Set<string>();\n slots.forEach((slot, index) => {\n const key = read(slot)?.trim();\n if (!key) return;\n if (first.has(key)) duplicates.add(key);\n else first.set(key, index);\n });\n for (const duplicate of duplicates) first.delete(duplicate);\n return first;\n };\n\n const match = (read: (slot: MeshMaterialSlotTopologyEntry) => string | undefined): void => {\n const oldByKey = uniqueIndex(previous, read);\n const nextByKey = uniqueIndex(current, read);\n for (const [key, nextIndex] of nextByKey) {\n if (mapping[nextIndex] !== -1) continue;\n const oldIndex = oldByKey.get(key);\n if (oldIndex === undefined || usedPrevious.has(oldIndex)) continue;\n mapping[nextIndex] = oldIndex;\n usedPrevious.add(oldIndex);\n }\n };\n\n match((slot) => slot.sourceKey);\n match((slot) => slot.slotName);\n\n const unmatchedCurrent = mapping\n .map((oldIndex, index) => (oldIndex === -1 ? index : -1))\n .filter((index) => index !== -1);\n const unmatchedPrevious = previous\n .map((_, index) => (usedPrevious.has(index) ? -1 : index))\n .filter((index) => index !== -1);\n if (\n unmatchedCurrent.length === 1 &&\n unmatchedPrevious.length === 1 &&\n current[unmatchedCurrent[0] as number]?.sourceKey === undefined &&\n previous[unmatchedPrevious[0] as number]?.sourceKey === undefined\n ) {\n mapping[unmatchedCurrent[0] as number] = unmatchedPrevious[0] as number;\n usedPrevious.add(unmatchedPrevious[0] as number);\n unmatchedCurrent.length = 0;\n unmatchedPrevious.length = 0;\n }\n const identityInsufficient =\n unmatchedCurrent.some((index) => current[index]?.sourceKey === undefined) &&\n unmatchedPrevious.some((index) => previous[index]?.sourceKey === undefined);\n if (unmatchedCurrent.length > 0 && unmatchedPrevious.length > 0 && identityInsufficient) {\n return {\n ok: false,\n error: {\n code: 'mesh-material-slot-topology-change',\n previousIndices: unmatchedPrevious,\n nextIndices: unmatchedCurrent,\n hint: 'name source materials uniquely or provide stable sourceKey values before reimport',\n },\n };\n }\n\n for (let currentIndex = 0; currentIndex < current.length; currentIndex++) {\n let stableIndex = mapping[currentIndex] as number;\n if (stableIndex === -1) {\n stableIndex = stable.length;\n mapping[currentIndex] = stableIndex;\n }\n stable[stableIndex] = current[currentIndex] as MeshMaterialSlotTopologyEntry;\n }\n return { ok: true, slots: stable, currentToStableSlot: mapping };\n}\n\nexport interface MeshMaterialOverrideMigrationContext {\n readonly meshGuid: string;\n readonly sceneGuid: string;\n readonly entityId: number;\n}\n\nexport interface MeshMaterialOverrideConflict extends MeshMaterialOverrideMigrationContext {\n readonly code: 'mesh-material-slot-override-conflict';\n readonly materialSlot: number;\n readonly submeshIndices: readonly number[];\n readonly overrideHandles?: readonly number[];\n readonly overrideGuids?: readonly string[];\n readonly hint: string;\n}\n\nexport type MeshMaterialOverrideMigrationResult<T extends number | string = number> =\n | { readonly ok: true; readonly overrides: readonly T[] }\n | { readonly ok: false; readonly error: MeshMaterialOverrideConflict };\n\n/**\n * Collapse legacy per-submesh overrides into v3 per-slot overrides.\n * Conflicting section overrides fail atomically with full dependency context.\n */\nexport function migrateLegacyMeshMaterialOverrides<T extends number | string>(\n legacyOverrides: readonly T[],\n submeshes: readonly Pick<Submesh, 'materialSlot'>[],\n materialSlotCount: number,\n context: MeshMaterialOverrideMigrationContext,\n): MeshMaterialOverrideMigrationResult<T> {\n const inherited = (typeof legacyOverrides[0] === 'string' ? '' : 0) as T;\n const overrides = new Array<T>(materialSlotCount).fill(inherited);\n const sectionsBySlot = new Map<number, number[]>();\n for (let submeshIndex = 0; submeshIndex < submeshes.length; submeshIndex++) {\n const materialSlot = submeshes[submeshIndex]?.materialSlot;\n if (materialSlot === undefined || materialSlot < 0 || materialSlot >= materialSlotCount)\n continue;\n const sections = sectionsBySlot.get(materialSlot) ?? [];\n sections.push(submeshIndex);\n sectionsBySlot.set(materialSlot, sections);\n }\n for (const [materialSlot, submeshIndices] of sectionsBySlot) {\n const handles: T[] = submeshIndices.map((index) => legacyOverrides[index] ?? inherited);\n const distinct = [...new Set(handles)];\n if (distinct.length > 1) {\n return {\n ok: false,\n error: {\n code: 'mesh-material-slot-override-conflict',\n ...context,\n materialSlot,\n submeshIndices,\n ...(typeof handles[0] === 'string'\n ? { overrideGuids: handles as string[] }\n : { overrideHandles: handles as number[] }),\n hint: 'split the source slot or choose one override explicitly before v2 to v3 recook',\n },\n };\n }\n overrides[materialSlot] = distinct[0] ?? inherited;\n }\n return { ok: true, overrides };\n}\n\n/**\n * Submesh partitions a mesh's index/vertex range into an independent draw\n * call with its own primitive topology.\n *\n * Every field is required -- there is no default topology; the caller\n * must state the intended primitive type explicitly (charter P3 explicit\n * failure: silent default would mask topology mistakes).\n *\n * Naming aligns with Unity `SubMeshDescriptor` (without firstVertex /\n * baseVertex / bounds which are out of scope per OOS-3/OOS-4).\n *\n * | field | description |\n * |:--|:--|\n * | `indexOffset` | Start offset into the parent mesh's index buffer (in elements, not bytes). For vertex-only (non-indexed) submeshes, set to 0. |\n * | `indexCount` | Number of indices consumed from the index buffer starting at `indexOffset`. For vertex-only submeshes, set to 0. |\n * | `vertexCount` | Number of vertices spanned by this submesh range (used for the non-indexed draw path and for index-range OOB validation). |\n * | `topology` | GPU primitive topology for this submesh (one of the 5 WebGPU primitives: 'point-list' \\| 'line-list' \\| 'line-strip' \\| 'triangle-list' \\| 'triangle-strip'). |\n */\nexport interface Submesh {\n readonly indexOffset: number;\n readonly indexCount: number;\n readonly vertexCount: number;\n readonly topology: PrimitiveTopology;\n /** Index into the owning MeshAsset.materialSlots table. */\n readonly materialSlot: number;\n}\n\n/**\n * Texture asset POD shape aligned with `@webgpu/types ^0.1.69`\n * `GPUTextureDescriptor` subset (plan-strategy D-P1; RHI form rule\n * \"spec-aligned\"). Carries the decoded pixel bytes ready for\n * `GPUQueue.writeTexture` / `copyExternalImageToTexture` upload.\n *\n * `format` is the `GPUTextureFormat` string-literal union; `data` holds the\n * CPU-side decoded pixels (tight-packed, srgb-premultiplied-alpha-false per\n * D-P9). Optional `mipLevelCount` / `sampleCount` default to 1 at upload\n * time; v1 registers only 2D textures (depth / 3D / cube array are future\n * spinoffs).\n *\n * feat-20260515-learn-render-getting-started M3 / T-M3-03 minor-add (Asset\n * closed-union member count unchanged at 5; plan-strategy section 2.5 D Open\n * Q-4 selection (c)):\n * - `colorSpace: 'srgb' | 'linear'` -- AI-user-semantic SSOT (charter P4\n * consistent abstraction); `format='*-srgb'` family <-> `colorSpace='srgb'`\n * enforced by `AssetRegistry.uploadTexture` consistency assertion.\n * - `mipmap: boolean` -- `true` enables runtime mipmap-generator blit chain\n * (research F-1 SSOT three-source convergence; plan-strategy section 2.6\n * D Open Q-5 (a) independent file). `false` ships the single mip level\n * authored in `data`.\n *\n * Both fields are required (no default value) so consumers always make the\n * decision explicit at register-time (charter P3 explicit failure: silent\n * default would mask sRGB encode mistakes).\n */\nexport interface TextureAsset {\n readonly kind: 'texture';\n readonly width: number;\n readonly height: number;\n readonly format: GPUTextureFormat;\n readonly data: Uint8Array | Uint8ClampedArray;\n readonly colorSpace: 'srgb' | 'linear';\n readonly mipmap: boolean;\n readonly mipLevelCount?: number;\n readonly sampleCount?: number;\n}\n\n/**\n * Equirectangular environment-map asset POD shape -- a single 2D HDR image in\n * latitude-longitude projection (feat-20260630-equirect-kind-internalized-ibl-\n * declarative-skyligh M1, replacing the prior `CubeTextureAsset`).\n *\n * `kind:'equirect'` is the build-time-imported `.hdr` artefact: a single\n * `rgba16float` 2D image whose pixels live in `data` (tight-packed, build .bin).\n * Unlike the retired cube-texture, an equirect HAS a single 2D representation,\n * so it folds to a build-time `.bin` like `TextureAsset` (the cube-to-cube IBL\n * projection is a GPU-side pass driven internally by the render-system record\n * arm; AI users declare `Skylight{equirect}` rather than calling an upload).\n *\n * Fields mirror the `TextureAsset` 2D-image surface (charter P4 consistent\n * abstraction): `width` / `height` / `format` / `data` / `colorSpace`. No\n * `mipmap` chain field -- the IBL prefilter mip chain is a GPU-side pass, not a\n * CPU-authored level set.\n */\nexport interface EquirectAsset {\n readonly kind: 'equirect';\n readonly width: number;\n readonly height: number;\n readonly format: GPUTextureFormat;\n readonly data: Uint8Array | Uint8ClampedArray;\n readonly colorSpace: 'srgb' | 'linear';\n}\n\n/**\n * Sampler asset POD shape aligned with `@webgpu/types ^0.1.69`\n * `GPUSamplerDescriptor` subset. AI users supply filter + address modes;\n * the RHI layer materialises the GPU-side sampler on upload.\n */\nexport interface SamplerAsset {\n readonly kind: 'sampler';\n readonly magFilter?: GPUFilterMode;\n readonly minFilter?: GPUFilterMode;\n readonly mipmapFilter?: GPUMipmapFilterMode;\n readonly addressModeU?: GPUAddressMode;\n readonly addressModeV?: GPUAddressMode;\n readonly addressModeW?: GPUAddressMode;\n readonly lodMinClamp?: number;\n readonly lodMaxClamp?: number;\n readonly compare?: GPUCompareFunction;\n readonly maxAnisotropy?: number;\n}\n\n// feat-20260613 fix-issue-4: MATERIAL_PARAM_TYPES_V1 (9-Set) deleted —\n// MATERIAL_PARAM_TYPES (14-tuple, declared below) is the single SSOT.\n// §Change stance forbids v1/v2 dual-paths; the 9 v1 literals are a strict\n// subset of the 14-tuple, so all consumers (buildMaterialAssetValidator,\n// scanner.ts) migrate to the 14-tuple in one cut.\n\n// === MaterialParamType v2 (feat-20260613-material-paramschema-driven-binding M1 / w2) ===\n//\n// Decision anchors:\n// - plan-strategy D-7 paramSchema type set v2 (9 v1 + 5 new = 14 literals)\n// - research finding F-1 the union of all binding types used by the 5 built-in\n// shaders (standard-pbr / pbr-skin / unlit / sprite / shadow-caster) is exactly\n// these 14 entries; CSM (texture_depth_2d) / point shadow (texture_cube_array) /\n// IBL (texture_cube) / sampler_comparison / storage_buffer (skin palette) all\n// already exist downstream\n// - charter P3 explicit failure: closed unions guard exhaustive switching with\n// no default arm; TS verifies completeness\n//\n// Shape:\n// - `MATERIAL_PARAM_TYPES` : 14-element readonly tuple, the SSOT whitelist\n// - `MaterialParamType` : string-literal union derived from the tuple\n// - `NumericParamType` : 7 numeric literals (run-merged into one UBO entry)\n// - `TextureBindingParamType`: 6 literals — texture* + sampler*\n// (per D-4 each texture* auto-pairs a filtering sampler in derive output;\n// sampler / sampler_comparison are user-declared schema entries)\n// - `StorageBindingParamType`: storage_buffer (independent binding)\n// - `ParamSchemaEntry` : discriminated union over the three families\n// (Numeric / TextureBinding / StorageBinding) — exhaustive switching\n// on `entry.type` is closed across the 14 literals.\n\n/** 7 numeric WGSL types — std140-packed into one merged UBO entry (D-3). */\nexport type NumericParamType = 'f32' | 'i32' | 'u32' | 'vec2' | 'vec3' | 'vec4' | 'color';\n\n/** 6 texture-binding-family WGSL types: 4 texture views + 2 sampler kinds. */\nexport type TextureBindingParamType =\n | 'texture2d'\n | 'texture_cube'\n | 'texture_depth_2d'\n | 'texture_cube_array'\n | 'sampler'\n | 'sampler_comparison';\n\n/** 1 storage-binding type (e.g. skin palette buffer). */\nexport type StorageBindingParamType = 'storage_buffer';\n\n/**\n * Closed union of WGSL material-parameter type literals (14 members).\n * Every paramSchema entry's `type` field MUST be a member of this union.\n */\nexport type MaterialParamType =\n | NumericParamType\n | TextureBindingParamType\n | StorageBindingParamType;\n\n/**\n * v2 material parameter type whitelist — 14 ordered literal tuple (D-7).\n * Order is significant only as a stable enumeration source for tests\n * and discoverability; consumers should treat membership as a Set.\n */\nexport const MATERIAL_PARAM_TYPES = [\n 'f32',\n 'i32',\n 'u32',\n 'vec2',\n 'vec3',\n 'vec4',\n 'color',\n 'texture2d',\n 'texture_cube',\n 'texture_depth_2d',\n 'texture_cube_array',\n 'sampler',\n 'sampler_comparison',\n 'storage_buffer',\n] as const satisfies readonly MaterialParamType[];\n\n// Numeric-family schema entry (run-merged into a single UBO entry by derive).\n// `default` is optional; when present, values may omit the key.\n// - scalar numeric (f32 / i32 / u32) defaults to a single number\n// - vector + color types default to a length-N number tuple\nexport interface NumericParamSchemaEntry {\n readonly name: string;\n readonly type: NumericParamType;\n /** Asset-side color transfer function; runtime UBO values are always linear. */\n readonly colorSpace?: 'srgb' | 'linear';\n readonly default?: number | readonly number[];\n}\n\n// Texture-binding-family schema entry (texture* / sampler*).\n// `default` is kept optional for backward compatibility with existing\n// schema fixtures that carry stray defaults; derive ignores it for the\n// non-numeric families (D-4 auto-pair rule resolves samplers; textures\n// resolve via Handle<TextureAsset>).\nexport interface TextureBindingParamSchemaEntry {\n readonly name: string;\n readonly type: TextureBindingParamType;\n readonly default?: unknown;\n}\n\n// Storage-binding-family schema entry (storage_buffer).\n// Always an independent binding (not merged into the UBO run); `default`\n// is optional and ignored by derive (backward compatibility shim).\nexport interface StorageBindingParamSchemaEntry {\n readonly name: string;\n readonly type: StorageBindingParamType;\n readonly default?: unknown;\n}\n\n// Re-export derive(schema) and its output shapes alongside the schema\n// type union so all downstream consumers (runtime / vite-plugin-shader /\n// shader-compiler) reach the SSOT through a single import surface (D-2).\nexport type {\n DerivedMaterialInterface,\n DerivedNumericMember,\n DeriveOutput,\n ImmutableParamSchemaProjection,\n MaterialCoordinateRecordLayout,\n MaterialParameterProjection,\n MaterialParameterResourceProjection,\n MaterialParameterTextureProjection,\n MaterialResourceBindingLayout,\n MaterialResourceKind,\n ParamSchemaDeriveObservationKind,\n ParamSchemaDeriveObserver,\n ParamSchemaProjectionOwnerStats,\n UboFieldLayout,\n UboLayout,\n} from './derive-paramschema.js';\nexport {\n derive,\n deriveObserved,\n findUndeclaredSampledTextures,\n inferMaterialParameterKind,\n ParamSchemaProjectionOwner,\n} from './derive-paramschema.js';\n\n/**\n * Single material parameter schema entry — discriminated union over the\n * three families (Numeric / TextureBinding / StorageBinding).\n *\n * `name` is the parameter identifier matching a WGSL binding name.\n * `type` is the discriminator — exhaustive `switch (entry.type)` covers the\n * 14 literals without a `default` arm; TS guards completeness (charter P3).\n */\nexport type ParamSchemaEntry =\n | NumericParamSchemaEntry\n | TextureBindingParamSchemaEntry\n | StorageBindingParamSchemaEntry;\n\n// === RenderQueue namespace constants (feat-20260526-material-asset-multipass-renderstate M1 / w3) ===\n//\n// Decision anchors:\n// - requirements AC-04 (5 standard queue values: Background=1000 / Geometry=2000 /\n// AlphaTest=2450 / Transparent=3000 / Overlay=4000)\n// - plan-strategy D-3 (queue values replace three-bucket dispatch)\n// - research F-5 (Utopia queue model; Transparent=3000 gives gap between\n// AlphaTest=2450 and Transparent=3000 for user custom queues)\n// - charter P1 (progressive disclosure: RenderQueue.Geometry autocomplete\n// exposes the value; AI users never need to memorize bare numbers)\n\n/**\n * Standard render queue constants (AC-04). AI users access via IDE autocomplete\n * (`RenderQueue.`) — no bare numbers to memorize (charter P1 progressive disclosure).\n *\n * Queue order (ascending):\n * Background(1000) -> Geometry(2000) -> AlphaTest(2450) -> Transparent(3000) -> Overlay(4000)\n *\n * The gap between AlphaTest(2450) and Transparent(3000) allows user-inserted\n * custom queues without colliding with either boundary (research F-5).\n */\nexport const RenderQueue = {\n /** Skybox / backdrop draw, processed first. */\n Background: 1000,\n /** Opaque geometry draw — default queue for solid surfaces. */\n Geometry: 2000,\n /** Alpha-tested geometry (clip/discard in fragment shader) — drawn after opaque,\n * before transparent to avoid overdraw. */\n AlphaTest: 2450,\n /** Transparent / alpha-blended geometry — drawn back-to-front after opaque pass. */\n Transparent: 3000,\n /** Overlay / UI / debug lines — drawn last. */\n Overlay: 4000,\n} as const;\n\n/** Type alias for the 5-member RenderQueue value union (1000 | 2000 | 2450 | 3000 | 4000). */\nexport type RenderQueue = (typeof RenderQueue)[keyof typeof RenderQueue];\n\n// === MaterialRenderState POD interface (feat-20260526-material-asset-multipass-renderstate M1 / w1) ===\n//\n// Decision anchors:\n// - requirements AC-03 (all fields optional; engine applies known defaults)\n// - plan-strategy D-2 (MaterialRenderState fields optional + engine static defaults)\n// - research F-3 (current hardcoded values become the defaults)\n// - research F-6 (Three.js taxonomy proves this subset is sufficient for LO 4.x)\n// - charter P1 (all fields optional reduces boilerplate; JSDoc on each field exposes\n// the default so AI users discover via IDE hover without reading prose)\n\n/**\n * Stencil face state sub-interface — mirrors `@webgpu/types.GPUStencilFaceState`\n * field-by-field (spec-aligned per RHI form rules). All fields optional so\n * consumers declare only the stencil behavior they need.\n */\nexport interface StencilFaceState {\n /** Default: `'never'`. */\n readonly compare?: GPUCompareFunction;\n /** Default: `'keep'`. */\n readonly failOp?: GPUStencilOperation;\n /** Default: `'keep'`. */\n readonly depthFailOp?: GPUStencilOperation;\n /** Default: `'keep'`. */\n readonly passOp?: GPUStencilOperation;\n}\n\n/**\n * Material render-state POD interface — all fields optional (AC-03).\n *\n * When a field is `undefined`, the pipeline-builder falls back to the\n * engine default value noted in each field's JSDoc. AI users only override\n * the fields that differ from the defaults (charter P1 progressive disclosure).\n *\n * The defaults are:\n * - `cullMode='back'` (back-face culling per WebGPU convention)\n * - `depthCompare='less'` (standard depth testing)\n * - `depthWriteEnabled=true` (write depth for opaque surfaces)\n * - `blend` undefined (no blending — opaque pass)\n * - `stencil` undefined (no stencil operations)\n * - `stencilReadMask` undefined (WebGPU default 0xFFFFFFFF)\n * - `stencilWriteMask` undefined (WebGPU default 0xFFFFFFFF)\n * - `frontFace` undefined (default 'ccw')\n */\nexport interface MaterialRenderState {\n /** Face culling mode. Default: `'back'` (cull back faces). */\n readonly cullMode?: 'none' | 'front' | 'back';\n /** Depth comparison function. Default: `'less'`. */\n readonly depthCompare?: GPUCompareFunction;\n /** Whether depth writes are enabled. Default: `true`. */\n readonly depthWriteEnabled?: boolean;\n /** Blend state descriptor (spec-aligned with `GPUBlendState`). Default: undefined (no blending). */\n readonly blend?: GPUBlendState;\n /** Enable alpha-to-coverage when the active camera uses MSAA. Default: `false`. */\n readonly alphaToCoverageEnabled?: boolean;\n /** Stencil face state. Default: undefined (no stencil operations). */\n readonly stencil?: StencilFaceState;\n /**\n * Stencil read mask (mirrors GPUDepthStencilState.stencilReadMask top-level).\n * Default: undefined (WebGPU default 0xFFFFFFFF).\n */\n readonly stencilReadMask?: number;\n /**\n * Stencil write mask (mirrors GPUDepthStencilState.stencilWriteMask top-level).\n * Default: undefined (WebGPU default 0xFFFFFFFF).\n */\n readonly stencilWriteMask?: number;\n /**\n * Front-face winding (mirrors GPUPrimitiveState.frontFace).\n * Default: `'ccw'`.\n */\n readonly frontFace?: 'ccw' | 'cw';\n}\n\n// === PassKind as open string + KNOWN_PASS_KINDS (feat-20260615-pipeline-spec-ssot D-10) ===\n//\n// Decision anchors:\n// - plan-strategy D-10 (PassKind opened from closed union to string; KNOWN_PASS_KINDS\n// is a discoverable documentation constant)\n// - requirements AC-09 (PassKind: open string; unknown passKind -> PipelineSpecError\n// code='unknown-pass-kind')\n// - charter P3 (fail-fast on unknown passKind via PipelineSpecError, not silent route)\n\n/**\n * Render-pass kind -- open string, no longer a closed union.\n *\n * `KNOWN_PASS_KINDS` (below) documents the engine-shipped pass kinds; user-defined\n * pass kinds are supported through {@link ShaderRegistry} registration. An unknown\n * pass kind triggers {@link PipelineSpecError} with code `'unknown-pass-kind'`\n * at pipeline-spec build time (charter P3 explicit failure).\n *\n * @see {@link KNOWN_PASS_KINDS} for the discoverable pass-kind catalogue\n * @see plan-strategy D-10 (PassKind opened from closed union)\n */\nexport type PassKind = string;\n\n/**\n * Engine-shipped pass kinds -- discoverable constant catalogue (D-10).\n *\n * Consumers iterate or lookup against this set to validate pass kinds before\n * submitting to `getOrBuildPipeline`. An unknown pass kind still triggers a\n * structured `PipelineSpecError` with code `'unknown-pass-kind'` carrying\n * `.detail.expected = KNOWN_PASS_KINDS` and `.detail.actual`.\n */\nexport const KNOWN_PASS_KINDS: readonly string[] = [\n 'forward',\n 'deferred',\n 'temporal',\n 'lighting',\n 'shadow-caster',\n 'post-process',\n 'skybox',\n] as const;\n\n// === PassSelector type (feat-20260526-material-asset-multipass-renderstate M1 / w4) ===\n//\n// Decision anchors:\n// - requirements AC-05 (tags + PassSelector matching: all selector keys must\n// exist in pass tags with value in the selector's value list)\n// - plan-strategy D-4 (Tags free Record + PassSelector Record<string, string[]>;\n// enum-based categories rejected — adding a pipeline stage should not require\n// editing the types package)\n// - charter P1 (type signature itself is the match-rule documentation;\n// `Record<string, string[]>` is self-describing)\n\n/**\n * Pass selector — maps tag keys to allowed value lists (AC-05).\n *\n * A pass matches the selector when **every** key in the selector exists in the\n * pass's `tags` and the pass's tag value is in the selector's value list for that key.\n * An empty selector matches every pass (no key constraints).\n *\n * The type signature itself is the API docs (charter P1): each entry maps a\n * tag key (string) to its allowed values (string array).\n *\n * @example Match passes tagged with `RenderType: 'Opaque'` or `RenderType: 'Transparent'`\n * ```ts\n * const selector: PassSelector = { RenderType: ['Opaque', 'Transparent'] };\n * ```\n */\nexport type PassSelector = Record<string, readonly string[]>;\n\n// === FontAsset POD shape (feat-20260531-world-space-msdf-text-rendering M2 / w5) ===\n//\n// Decision anchors:\n// - plan-strategy D-6 (FontAsset data shape: atlas Handle<TextureAsset> +\n// sampler Handle<SamplerAsset> + glyphs Record<codepoint, GlyphMetric> +\n// common block + optional notdef fallback; POD, math-free, fields 1:1\n// mirror toolchain wiki §4 BMFont char mapping)\n// - requirements AC-04 (FontAsset enters Asset closed union, 11->12)\n// - AGENTS.md §Component naming (single-semantic components drop the\n// Component suffix; FontAsset is a data asset, not an ECS component)\n//\n// GlyphMetric fields mirror the BMFont char block layout (toolchain wiki §4):\n// advance <- xadvance (horizontal distance to next glyph)\n// bearingX <- xoffset (horizontal offset from cursor)\n// bearingY <- yoffset (vertical offset from baseline)\n// size.{w,h} <- width/height (glyph quad size in layout space, before atlas\n// scale)\n// region.{x,y,w,h} <- atlas UV region in pixels (x/y = top-left corner\n// relative to atlas origin)\n\n/**\n * Per-glyph metric layout — 1:1 mirror of BMFont char block fields\n * (toolchain wiki §4). POD, math-free.\n */\nexport interface GlyphMetric {\n /** Horizontal distance to the next glyph (xadvance). */\n readonly advance: number;\n /** Horizontal offset from cursor (xoffset). */\n readonly bearingX: number;\n /** Vertical offset from baseline (yoffset). */\n readonly bearingY: number;\n /** Glyph quad size in layout space. */\n readonly size: { readonly w: number; readonly h: number };\n /** Atlas UV region in pixels (top-left origin). */\n readonly region: {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n };\n}\n\n/**\n * Font asset POD — atlas texture handle + sampler handle + per-codepoint\n * glyph metrics + common layout block.\n *\n * AI users obtain a FontAsset via `assets.load(guid, fontAssetKind)` and\n * hand the resulting `Handle<FontAsset>` to `GlyphText.fontHandle`. The atlas\n * texture and sampler are resolved through the handle chain by the glyph\n * layout system; the per-glyph metrics drive the quad-position-and-UV baking\n * (plan-strategy D-6).\n *\n * | Field | Purpose |\n * |:--|:--|\n * | `atlas` | Handle to the baked MSDF atlas `TextureAsset` |\n * | `sampler` | Handle to the `SamplerAsset` for atlas sampling |\n * | `glyphs` | `Record<codepoint, GlyphMetric>` — O(1) codepoint lookup |\n * | `common` | Common layout block (lineHeight / base / distanceRange / pxRange / atlas width/height) |\n * | `notdef` | Optional fallback glyph metric for missing codepoints (TOFU, AC-14) |\n */\nexport interface FontAsset {\n readonly kind: 'font';\n /**\n * Atlas texture GUID (D-19). Payload-internal sub-asset ref stored as an\n * AssetGuid, not a handle — the typed load recursion mints no handle; the\n * World-holding consumer (glyph-text-layout) resolves it once at read time.\n */\n readonly atlas: AssetGuid;\n /** Sampler GUID (D-19). Same GUID-identity contract as `atlas`. */\n readonly sampler: AssetGuid;\n readonly glyphs: Record<number, GlyphMetric>;\n readonly common: {\n readonly lineHeight: number;\n readonly base: number;\n readonly distanceRange: number;\n readonly pxRange: number;\n readonly atlasWidth: number;\n readonly atlasHeight: number;\n };\n readonly notdef?: GlyphMetric;\n}\n\n// === RenderPipelineAsset POD shape =============================================\n//\n// feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend M1 / w5.\n// Asset-layer descriptor that binds a registered render-pipeline logic id to an\n// installable Handle (the MaterialAsset { materialShaderId, params } pattern, scaled to\n// the pipeline layer). `installPipeline(handle)` resolves this POD off the AssetRegistry,\n// looks up `pipelineId` in the pipeline registry, and swaps the per-frame graph.\n//\n// M2 stage (w10): `config.passCount` is the FIRST real config key. The standard pipeline\n// runs with config undefined (default frame byte-identical, AC-01); a custom pipeline can\n// size its declared pass chain from `config.passCount` so its topology varies observably\n// via `renderer.perFramePassNames` (AC-03 / plan-strategy D-C).\n//\n// feat-20260608-cluster-lighting M2 / w8: `config.clusterGrid` is the HDRP cluster grid\n// dimensions config (default {x:16, y:9, z:24}). `pipelineId` is narrowed to a literal\n// union of `'forgeax::urp' | 'forgeax::hdrp' | (string & {})` so TS narrowing on\n// `pipelineId === 'forgeax::hdrp'` narrows `config.clusterGrid`.\n//\n// feat-20260612-hdrp-ssao M4 / w19: `config.ssao` is the SSAO configuration\n// (enabled+radius+bias+intensity). Same shared-config pattern as clusterGrid;\n// HDRP consumes it, URP ignores it at runtime.\n/**\n * Asset-layer descriptor for an installable render pipeline.\n *\n * `pipelineId` references a logic registered via `renderer.registerPipeline(id, impl)`\n * (engine builtins use the `forgeax::` prefix, e.g. `'forgeax::urp'`; user pipelines use\n * `<package>::<id>`). `config` is the per-install tuning the pipeline logic reads at\n * `buildGraph` time; `passCount` is the first real key (a custom pipeline declares that\n * many passes). The URP ignores `config` (its topology is fixed).\n *\n * `config.clusterGrid` is the HDRP cluster grid dimensions (x/y/z each an integer in\n * [1, 64]; default {16, 9, 24}). It is ignored by URP.\n *\n * `config.ssao` enables SSAO (Screen-Space Ambient Occlusion) for the HDRP\n * deferred path. Ignored by URP.\n */\nexport interface RenderPipelineAsset {\n readonly kind: 'render-pipeline';\n readonly pipelineId: 'forgeax::urp' | 'forgeax::hdrp' | (string & {});\n readonly config?: {\n readonly passCount?: number;\n readonly clusterGrid?: { readonly x: number; readonly y: number; readonly z: number };\n readonly ssao?: {\n readonly enabled: boolean;\n readonly radius?: number | undefined;\n readonly bias?: number | undefined;\n readonly intensity?: number | undefined;\n };\n /**\n * feat-20260621 M4': ordered registered post-process shader ids the built-in\n * pipelines composite over the FINAL swap-chain image, after the fxaa pass\n * and before the debug overlay. Each id must be registered via\n * `renderer.postProcess.register(id, entry)` first. The effects run in array\n * order; each samples the current swap-chain (copy) and writes it back, so a\n * chain composes left-to-right. This is the AUGMENT path: the built-in 9-pass\n * chain (shadow cascades, tonemap, bloom, fxaa) renders unchanged and the\n * effects layer on top — unlike installing a wholly custom pipeline, which\n * REPLACES the built-in graph (and would drop its shadow passes). `undefined`\n * or `[]` adds zero passes (default frame byte-identical).\n *\n * WebGPU backend only: each effect reads the mid-frame swap-chain (copy +\n * non-srgb storage-view write), which the WebGL2 fallback swap-chain does not\n * support (no COPY_SRC, no non-srgb reinterpret view) — same constraint the\n * built-in FXAA pass already carries. On a non-WebGPU device leave this empty.\n */\n readonly postEffects?: readonly string[];\n };\n}\n\n/**\n * Asset discriminated union - 13 variants keyed on `.kind`.\n *\n * Variant history:\n * - feat-20260513-instanced-mesh M1 introduced a 5th `'instanced-buffer-asset'`\n * variant carrying packed mat4 transforms + a `version` dirty flag.\n * - feat-20260514-ecs-children-instances-managed-buffer-array M3 (w15)\n * retired that variant: per-entity instanced transforms are now stored\n * directly inside the ECS via the `Instances { transforms: 'array<f32>' }`\n * component (managed by the BufferPool slot column + sidecar count\n * column). Asset closed-union shrinks 5 -> 4 (evolution major rename\n * per AGENTS.md `Change stance`); existing exhaustive `switch\n * (asset.kind)` consumers drop the now-unreachable arm in the same PR.\n * - feat-20260514-scene-as-world-blueprint w3 added `'scene'` variant\n * (4 -> 5, minor add per AGENTS.md `Evolution contract`); declarative\n * SceneEntity list, no overrides at the asset layer.\n * - feat-20260531-world-space-msdf-text-rendering w5 added `'font'` variant\n * (11 -> 12, minor add per AGENTS.md `Evolution contract`);\n * FontAsset with atlas handle + sampler handle + glyph metrics.\n * - feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend w5 added\n * `'render-pipeline'` variant (12 -> 13, minor add per AGENTS.md\n * `Evolution contract`); RenderPipelineAsset with pipelineId + config.\n * - feat-20260623-world-space-video-asset M1 added `'video'` variant\n * (14 -> 15, minor add per AGENTS.md `Evolution contract`);\n * VideoAsset with `{ url }` descriptor, no width/height/duration.\n *\n * Exhaustive `switch (asset.kind)` type-guards against future additions\n * without default fallback (charter proposition 4 + proposition 3).\n *\n * | kind | variant |\n * |:--|:--|\n * | `'mesh'` | `MeshAsset` |\n * | `'texture'` | `TextureAsset` |\n * | `'equirect'` | `EquirectAsset` (single 2D HDR lat-long env map for IBL) |\n * | `'sampler'` | `SamplerAsset` |\n * | `'material'` | `MaterialAsset` (further narrows on `.passes`) |\n * | `'scene'` | `SceneAsset` (declarative SceneEntity list, no overrides) |\n * | `'font'` | `FontAsset` (MSDF atlas handle + glyph metrics) |\n * | `'render-pipeline'` | `RenderPipelineAsset` (installable pipeline logic id + config) |\n * | `'video'` | `VideoAsset` (runtime-only `{ url }` descriptor, no width/height/duration) |\n * | `'animation-graph'` | `AnimationGraph` (Clip/Blend/Add DAG + per-node static weights) |\n */\nexport type Asset =\n | MeshAsset\n | TextureAsset\n | EquirectAsset\n | SamplerAsset\n | MaterialAsset\n | SceneAsset\n | SkeletonAsset\n | SkinAsset\n | AnimationClip\n // === 1 new variant (feat-20260713-animation-state-machine-plugin M2 / w13) ===\n // AnimationGraph POD (Clip/Blend/Add DAG); GUID-addressable, serializable\n // (AC-14 foundation). Closed node union (clip/blend/add), no reserved FSM/Mask\n // fields (OOS-1..4).\n | AnimationGraph\n | AudioClipAsset\n | FontAsset\n | RenderPipelineAsset\n // === 1 new variant (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n // Direct atlases[] form (plan-strategy D-7 one-cut); no intermediate single-`atlas` shape.\n | TilesetAsset\n // === 1 new variant (feat-20260623-world-space-video-asset M1) ===\n // runtime-only { url } descriptor; no width/height/duration in payload.\n | VideoAsset\n | ParticleEffectAsset;\n\n// === Tileset asset POD shape (feat-20260608 M0 baseline rebuild) =================\n//\n// Decision anchors:\n// - requirements §AC-01/03/04/05 (TilesetAsset 9 fields; atlases plural composite;\n// M0 TilesetTileEntry single-field shape with M1 adding 5 optional + collider).\n// - plan-strategy §D-5 (M0 baseline rebuild after main reverted feat-20260604).\n// - plan-strategy §D-7 (`atlases: readonly Handle<TextureAsset,managed>[]` one-cut\n// rename; no `atlas` single-form alias or dual-path).\n// - plan-strategy §D-6 (AssetErrorCode count restoration -- M0 reintroduces\n// `tileset-region-index-out-of-range`; M1 adds `tileset-tile-entry-malformed`).\n// - charter F1 (AI users discover the schema via IDE autocomplete on the closed\n// `Asset` union + `Handle<TilesetAsset>` returns from `AssetRegistry.register`).\n// - charter P4 (atlases plural composite mirrors `MaterialAsset.passes[]` shape).\n\n/**\n * Closed `TilesetTileCollider` union -- per-tile collider schema (M1\n * extension; feat-20260608-tilemap-object-layer-rendering M1 / m1-t2).\n *\n * Three discriminant variants (closed enum, charter P3):\n *\n * - `{ type: 'none' }` -- no collider for this tile.\n * - `{ type: 'rect', rect: readonly [x, y, w, h] }` -- axis-aligned\n * rectangle in normalized cell coordinates `[0, 1]^2`. `w > 0`, `h > 0`,\n * `x + w <= 1`, `y + h <= 1` are enforced by `validateTilesetPayload`\n * (R-6 first-error path).\n * - `{ type: 'polygon', points: readonly [x, y][] }` -- convex/concave\n * polygon in normalized cell coordinates. `points.length >= 3` and\n * each point lies in `[0, 1]^2`.\n *\n * The engine validates this schema at register-time but does NOT consume\n * it (plan-strategy §D-4 -- schema landed, consumer deferred to a future\n * `feat-tilemap-physics-bridge` closed loop). AI users with a physics\n * sidecar consume the schema directly via `tileset.tiles[i].collider`\n * after `assets.register<TilesetAsset>(...)` resolves the handle.\n *\n * Exhaustive switch:\n * ```ts\n * switch (collider.type) {\n * case 'none': return null;\n * case 'rect': return collider.rect;\n * case 'polygon': return collider.points;\n * // No default branch -- TS guards completeness (charter P3).\n * }\n * ```\n */\nexport type TilesetTileCollider =\n | { readonly type: 'none' }\n | { readonly type: 'rect'; readonly rect: readonly [number, number, number, number] }\n | { readonly type: 'polygon'; readonly points: readonly (readonly [number, number])[] };\n\n/**\n * Rectangular sub-region within a tileset atlas (M1 schema extension on\n * top of M0 baseline rebuild).\n *\n * Four required fields define the atlas-space rectangle in pixels:\n * `x` / `y` top-left corner; `width` / `height` extent. `width + x` and\n * `height + y` MUST stay within the parent atlas extent or\n * `validateTilesetPayload` returns\n * `AssetError { code: 'tileset-region-index-out-of-range' }`\n * (charter P3 explicit failure at register-time).\n *\n * Optional `atlasIndex?: number` (M1; default 0) routes the region into\n * `TilesetAsset.atlases[atlasIndex]` for multi-atlas tilesets. Out-of-range\n * `atlasIndex` (`>= atlases.length` or negative) surfaces\n * `AssetError { code: 'tileset-tile-entry-malformed', detail: { field: 'atlasIndex', scope: 'tileset-asset' } }`\n * at register-time (plan-strategy §D-7 three-hop routing; R-6 first-error\n * order places atlasIndex check between region rect bounds and per-tile\n * entry field checks).\n */\nexport interface TilesetRegion {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly atlasIndex?: number;\n}\n\n/**\n * Per-tile entry in `TilesetAsset.tiles[]` (M1 schema extension on top of\n * M0 baseline rebuild).\n *\n * Required `regionIndex` points into the parent `TilesetAsset.regions[]`\n * array. M1 adds five optional fields for the variable-size + custom-pivot\n * object-layer story (plan-strategy §M1):\n *\n * - `widthCells?: number` -- multi-cell width in `Tilemap.cols/rows`\n * coordinate units (default 1; range `(0, 64]`). Anchored at the\n * cell that hosts the non-zero tileId entry; cells inside the\n * `widthCells x heightCells` footprint must stay 0 in `TileLayer.tiles[]`\n * (anchor convention, not enforced at register time).\n * - `heightCells?: number` -- multi-cell height (default 1; range `(0, 64]`).\n * - `pivotX?: number` -- normalized horizontal pivot in `[0, 1]` (default 0.5).\n * The pivot is the world-space anchor: `pivot_world_X = (cellX + pivotX) * tileSizeX`.\n * Quad center is offset by `(0.5 - pivotX) * widthCells * tileSizeX`\n * (plan-strategy §D-2 first-line geometry; M2 implementation).\n * - `pivotY?: number` -- normalized vertical pivot in `[0, 1]` (default 0.5).\n * For asi_world `.tsj` extension: `pivotY = 1.0` means quad bottom\n * anchors the cell, `pivotY = 0.0` means quad top (per-asset convention,\n * not Tiled native). The engine uses `effectivePivotY` for Y-sort.\n * - `collider?: TilesetTileCollider` -- 3-variant closed union schema\n * (charter P3). Engine validates the schema at register-time but does\n * NOT consume it (plan-strategy §D-4).\n *\n * All five fields are optional so unit-cell call sites `{ regionIndex: N }`\n * remain backward compatible (charter F1).\n *\n * Out-of-range `regionIndex` (>= regions.length, or negative) surfaces\n * `AssetError { code: 'tileset-region-index-out-of-range' }`. Out-of-range\n * `widthCells / heightCells / pivotX / pivotY / collider` surface\n * `AssetError { code: 'tileset-tile-entry-malformed', detail: { field, scope: 'tile-entry', tileEntryIndex } }`\n * (plan-strategy §D-6 closed 7-variant `.detail.field` enum).\n */\nexport interface TilesetTileEntry {\n readonly regionIndex: number;\n readonly widthCells?: number;\n readonly heightCells?: number;\n readonly pivotX?: number;\n readonly pivotY?: number;\n readonly collider?: TilesetTileCollider;\n}\n\n/**\n * Tileset asset (M0 baseline rebuild on origin/main).\n *\n * Nine fields:\n * - `kind` -- discriminator literal `'tileset'`.\n * - `atlases` -- one or more durable GUIDs for atlas textures.\n * `atlases.length >= 1` enforced at register time.\n * M0 reads `atlases[0]` exclusively (single-atlas form);\n * M1 adds `regions[].atlasIndex` for multi-atlas routing.\n * - `tileWidth`/`tileHeight` -- per-cell pixel size (atlas grid stride).\n * - `columns`/`rows` -- atlas grid layout (informational metadata; used\n * as fallback when `atlasSizes` is absent to infer\n * atlas pixel extent as `columns * tileWidth`).\n * - `atlasSizes` -- optional per-atlas pixel dimensions. When present,\n * `atlasSizes[i]` gives the exact pixel size of\n * `atlases[i]` and overrides `columns`/`rows` for UV\n * normalisation in the chunk-extract system. Required when\n * the tileset contains multiple atlases with different\n * pixel sizes (e.g. a terrain + object composite tileset).\n * Each entry carries `{ pixelWidth, pixelHeight }`.\n * - `regions` -- array of atlas sub-rectangles (TilesetRegion).\n * - `tiles` -- per-tile entries (TilesetTileEntry), 1-indexed via tile id\n * sentinel where 0 means \"empty\" in `TileLayer.tiles`.\n *\n * Plural composite `atlases` (not single `atlas`) is the one-cut breaking\n * rename versus the old feat-20260604 form (plan-strategy §D-7 + AGENTS.md\n * §Change stance \"optimal > compatible\"); no deprecation alias survives.\n *\n * @example Register a tileset and spawn a Tilemap + TileLayer pair:\n * ```ts\n * const atlasGuid = 'world/object_atlas';\n * const tileset = registry.register<TilesetAsset>({\n * kind: 'tileset',\n * atlases: [atlasGuid],\n * tileWidth: 16,\n * tileHeight: 16,\n * columns: 8,\n * rows: 8,\n * regions: [{ x: 0, y: 0, width: 16, height: 16 }],\n * tiles: [{ regionIndex: 0 }],\n * }).unwrap();\n * ```\n */\nexport interface TilesetAtlasSize {\n readonly pixelWidth: number;\n readonly pixelHeight: number;\n}\n\nexport interface TilesetAsset {\n readonly kind: 'tileset';\n readonly atlases: readonly string[];\n readonly tileWidth: number;\n readonly tileHeight: number;\n readonly columns: number;\n readonly rows: number;\n /** Per-atlas pixel dimensions. `atlasSizes[i]` overrides `columns`/`rows`\n * for UV normalisation of regions whose `atlasIndex === i`. Required when\n * atlases have different pixel sizes. */\n readonly atlasSizes?: readonly TilesetAtlasSize[];\n readonly regions: readonly TilesetRegion[];\n readonly tiles: readonly TilesetTileEntry[];\n}\n\n// === AssetRef + AssetEnvelope (feat-20260622-asset-ref-graph-protocol-unification-refs-as-ssot M1 / w1) ===\n//\n// Decision anchors:\n// - plan-strategy D-1: single envelope type AssetEnvelope = { guid, kind, name?,\n// payload, refs } — ImportedAsset is upgraded to this shape; assetCatalog\n// value type changes from Map<string, Asset> to Map<string, AssetEnvelope>.\n// - plan-strategy D-2: scene refs are importer flat superset (mesh U material U\n// texture U skeleton U skin); texture edges have sourceField=undefined (no\n// per-entity origin).\n// - plan-strategy D-3: sourceField is structured triple { componentName,\n// fieldName, arrayIndex? } — consumers read via property access, not string\n// parse (charter P3).\n// - plan-strategy D-10: edge metadata (AssetRef) does NOT sink into Loader.load\n// refs param — loader still receives GUID string projection.\n// - plan-strategy OOS-1: Asset closed union unchanged; AssetEnvelope wraps it.\n// - plan-strategy OOS-4: AssetErrorCode member set unchanged (21 members).\n// - charter F1: single-entry indexability — refs field name cross-layer\n// consistent (ImportedAsset.refs / AssetEnvelope.refs / pack-index refs).\n\n/**\n * Structured edge metadata carried in an asset envelope's `refs[]`. Each entry\n * records a GUID-level reference plus optional provenance: which scene entity\n * field originated the reference (``sourceField``) and the entity's local id\n * (``sceneEntityId``). Texture edges and other transitive references have no\n * per-entity origin — ``sourceField`` is ``undefined`` for those (D-2).\n *\n * AI users consume ``sourceField`` via property access (``ref.sourceField?.componentName``\n * / ``ref.sourceField?.fieldName`` / ``ref.sourceField?.arrayIndex``), never by\n * parsing a concatenated string (charter P3).\n */\nexport interface AssetRef {\n readonly guid: string;\n readonly sourceField?: {\n readonly componentName?: string;\n readonly fieldName: string;\n readonly arrayIndex?: number;\n };\n readonly sceneEntityId?: number;\n}\n\n/**\n * Self-contained asset envelope — the single shape through which assets flow\n * from import to catalog to recursive load (plan-strategy D-1).\n *\n * ``payload`` carries the closed ``Asset`` union member (mesh / texture / scene /\n * material / etc.). ``refs`` is the authoritative reference graph — every GUID\n * this asset transitively depends on, with optional edge metadata\n * (``sourceField`` / ``sceneEntityId``). ``name`` is the per-asset display name\n * (may be undefined; ``resolveName`` derives the final name via a three-argument\n * XOR that also considers the package path).\n */\nexport interface AssetEnvelope<P = Asset> {\n readonly guid: string;\n readonly kind: string;\n // Per-GUID stored display name -- the `storedName` argument resolveName feeds\n // to deriveAssetName (the single home for the explicit name, replacing the\n // retired storedNameOf side table). `undefined` = no explicit name (resolveName\n // then applies the multi-asset basename fallback / no-package '' branch).\n readonly name?: string;\n readonly payload: P;\n readonly refs: readonly AssetRef[];\n}\n\n// === Package interface (feat-20260618-asset-and-pack-name-fields M1 / w2) ======\n//\n// Decision anchors:\n// - plan-strategy D-7 (Package interface in @forgeax/engine-types, same layer\n// as Asset union, for multi-package consumer discoverability per charter F1)\n// - architecture-principles #2 (Derive, Don't Duplicate): assetCount is\n// derived from assetGuids.size, never stored independently\n// - plan-strategy D-5 (builtin assets -> null Package, not a synthetic path)\n// - Package does not carry a `name` field — resolved names flow through\n// resolveName (D-6), not stored on Package\n//\n// AI users discover Package via IDE autocomplete on @forgeax/engine-types;\n// the runtime AssetRegistry.packageOf Map carries Package | null per guid.\n\n/**\n * Runtime view of one import-source package -- the grouping unit for\n * the two-segment asset identity (`<packagePath>.<name>`).\n *\n * `path` is the import file path (e.g. `'assets/hero.glb'`). Multiple\n * assets imported from the same source file share one `Package`.\n *\n * `assetGuids` lists every GUID that belongs to this package. The\n * runtime keeps it in sync with `registerPackage` insertions.\n *\n * `assetCount` is a derived view (`assetGuids.size`); it is **not**\n * stored as a standalone field (Derive axiom #2).\n */\nexport interface Package {\n readonly path: string;\n readonly assetGuids: ReadonlySet<string>;\n readonly assetCount: number;\n}\n\n// === Scene asset POD shape (feat-20260514-scene-as-world-blueprint w2) ==========\n//\n// Decision anchors:\n// - requirements §AC-01 (AssetUnion 6 elements; SceneAsset top level only\n// `kind` + `entities`, no overrides field at the asset layer)\n// - requirements §AC-02 (LocalEntityId branded number; cross-brand assignment\n// to / from Entity is a TS compile-time error)\n// - charter proposition 1 (single-entry IDE autocomplete from\n// `@forgeax/engine-types`) + proposition 4 (explicit failure: brand\n// phantom rejects untagged number) + proposition 5 (consistent\n// abstraction, structurally parallel to Handle<T> brand)\n//\n// The unique-symbol brand stays private to this module so the brand\n// identity is anchored exactly here; consumers refer to LocalEntityId\n// as opaque number subtypes.\n\ndeclare const LocalEntityIdBrand: unique symbol;\n\n/**\n * Scene-local entity index brand (u32).\n *\n * Authored as `0..entities.length-1` inside a SceneAsset; runtime storage stays\n * a plain JS number (the phantom `[LocalEntityIdBrand]` is erased at runtime\n * but rejects cross-brand assignment with `Entity` at\n * the TS layer).\n *\n * AI users obtain LocalEntityId values from `SceneEntity.localId` accessors and\n * hand them back to `SceneEntity` manipulation methods; plain\n * `number` is not assignable to `LocalEntityId` by design — see\n * `packages/types/src/__tests__/scene-brand.test-d.ts` for the negative\n * assertions.\n */\nexport type LocalEntityId = number & { readonly [LocalEntityIdBrand]: void };\n\n/**\n * Open map shape from component name to the per-component value record\n * authored on a `SceneEntity` (feat-20260514 w2).\n *\n * The map is keyed by component-token name (`'Transform' | 'MeshFilter' |\n * 'ChildOf' | ...`) and each per-component record is a free-form\n * `Record<string, unknown>` POD shape; the precise field types live in the\n * ecs `defineComponent(...)` schema (one layer up). This package stays\n * math-free + ecs-free; the layered alignment with the ecs schema vocab is\n * documented in plan-strategy §3.1 types_pkg sub-graph and tested by w3 /\n * w22 at the ecs / runtime layer.\n *\n * Open shape is intentional: components evolve via add-only minor in their\n * own packages; locking this map to a closed union here would force an edit\n * in @forgeax/engine-types every time a new component appears (charter\n * proposition 5 consistent abstraction — registration discipline owned by\n * each component's defineComponent site).\n */\nexport type ComponentValuesMap = {\n readonly [componentName: string]: Readonly<Record<string, unknown>>;\n};\n\n/**\n * Single SceneEntity POD shape (feat-20260514 w2).\n *\n * Carries one `localId` (LocalEntityId brand) plus a partial map of explicit\n * component field values. The partial keying lets layer 1 (explicit) leave\n * any component absent so layer 2 (component-level defaults) and layer 3\n * (TS type defaults) can fill in the residual fields at instantiate time\n * (plan-strategy §default-values 4-layer fallback table; AC-07 / AC-11 /\n * AC-12 sites).\n */\nexport interface SceneEntity {\n readonly localId: LocalEntityId;\n readonly components: Partial<ComponentValuesMap>;\n}\n\n/**\n * One per-member mount-time modification (add-or-patch) applied to a\n * mounted scene member (feat-20260608-scene-nesting-ecs-fication M1 / w7;\n * feat-20260713-mount-override-component-add-and-shared-ref-round M1 / w2;\n * AC-01, AC-19).\n *\n * `localId` selects a member entity inside the mounted SceneAsset,\n * counted against the mount's `memberFirst` window. `comp` names the\n * target component. `field` is a component-granular discriminant:\n * - present -> PATCH one field: `value` is that single field's value,\n * written over the member's existing component at instantiate time;\n * - absent -> ADD/UPSERT the whole component: `value` is the per-field\n * value map for `comp`, merged onto (or creating) the member's\n * component.\n * `value` stays `unknown` because the per-component schema vocab lives one\n * layer up — runtime fail-fast via 'scene-override-type-mismatch' catches\n * type drift (plan-strategy D-9 / D-1). The discriminant is carried by the\n * shape itself (field present / absent), never a separate `op` tag, so no\n * consumer branches on a `switch (op)` (requirements: consumers do not\n * encode variant knowledge).\n *\n * Boundary notes: add is upsert (an existing component is merged, not\n * rejected); a NULL-sentinel entity value in the payload follows the same\n * `entity` remap rules as SceneEntity.components; overrides apply in\n * `mounts[].overrides[]` array order after the member's own authored\n * components.\n *\n * Charter mapping: proposition 1 (single-entry import surface, three-tier\n * progressive disclosure) + proposition 3 (machine-readable union,\n * MountOverride is a closed POD shape) + proposition 4 (explicit failure:\n * runtime apply path returns Result with structured error code).\n */\nexport interface MountOverride {\n readonly localId: LocalEntityId;\n readonly comp: string;\n readonly field?: string;\n readonly value: unknown;\n}\n\n/**\n * One mount instance authored on a parent SceneAsset\n * (feat-20260608-scene-nesting-ecs-fication M1 / w7; AC-01).\n *\n * A mount embeds another SceneAsset (referenced by `source`, a dual-carrier:\n * `number` = at-rest refs[] index; `string` = post-parse / post-collect GUID\n * string) into the parent's namespace. The mount reserves a contiguous\n * LocalEntityId window\n * `[memberFirst, memberFirst + memberCount)` for the embedded scene's\n * member entities so the parent SceneAsset's namespace invariant\n * `totalSlots = entities.length + mounts.length + sum(memberCount)`\n * holds (plan-strategy §6.3 + requirements §S-1).\n *\n * Optional fields:\n * - `parent`: LocalEntityId in the *parent* scene to which the mount\n * attaches (defaults to the parent scene's outermost root,\n * requirements-decisions §D-4);\n * - `components`: per-component value overlay applied to the mount\n * entity itself (mirrors SceneEntity.components shape; carries the\n * same Partial<ComponentValuesMap> typing);\n * - `overrides`: an array of MountOverride records that further\n * specialise individual member entities at mount-time (AC-19).\n *\n * Charter mapping: proposition 1 (single-entry import surface;\n * SceneInstanceMount sits next to SceneEntity / SceneAsset) +\n * proposition 5 (consistent abstraction: the field set mirrors\n * SceneEntity for AI-user discoverability).\n */\nexport interface SceneInstanceMount {\n readonly localId: LocalEntityId;\n readonly source: number | string;\n readonly memberFirst: LocalEntityId;\n readonly memberCount: number;\n readonly parent?: LocalEntityId;\n readonly components?: Partial<ComponentValuesMap>;\n readonly overrides?: readonly MountOverride[];\n /** Engine-owned publication identity for generated Scene mounts. */\n readonly publicationFence?: import('./asset-producer').ScenePublicationFence;\n}\n\n/**\n * Scene asset POD shape (feat-20260514 w2; sixth member of the closed\n * `Asset` union).\n *\n * Three top-level fields — `kind: 'scene'` discriminator,\n * `entities: readonly SceneEntity[]`, and the optional `mounts:\n * readonly SceneInstanceMount[]` (feat-20260608-scene-nesting-\n * ecs-fication M1 / w7; AC-01). Per-instance overrides at the\n * asset layer are still absent (charter proposition 5: ECS write\n * paths and prefab override paths are explicitly disjoint,\n * plan-strategy §3.2 sequence B); the `mounts[]` window is an\n * authoring-time graph edge, not a write path.\n *\n * Back-compat: `mounts` is optional so legacy SceneAsset values\n * remain assignable; missing `mounts` is semantically equivalent to\n * `mounts: []` (plan-strategy §6.3, ajv default `[]`).\n */\nexport interface SceneAsset {\n readonly kind: 'scene';\n readonly entities: readonly SceneEntity[];\n readonly mounts?: readonly SceneInstanceMount[];\n /**\n * GUIDs of `SkinAsset`s the scene's skinned entities reference (one per\n * SkeletonAsset bound by a `Skin: { skeleton }` component). SkinAssets are\n * not reachable through any `handle<*>` field on a SceneEntity component\n * (`Skin.skeleton` carries the SkeletonAsset GUID; the SkinAsset itself is\n * a sibling identified by matching `skeletonGuid`), so the scene's pack\n * load chain has to surface them explicitly. Without this list the\n * browser-async-pack-fetch path would never load SkinAssets, leaving\n * `postSpawnResolveJoints` unable to populate `Skin.joints[]` and the\n * extract pass fail-fasting on `Skin.joints.length=0` every frame\n * (feat-20260612-skin-palette-per-frame-upload M2 fixup).\n *\n * On disk: refs[] indices, mirror of `mounts[].source`.\n * Post-parseScenePayload: GUID strings (resolved via refs[]).\n * Enumerated in the scene envelope's `refs[]` (the recursion source) so\n * `AssetRegistry.load(sceneGuid, sceneAssetKind)` recursively pulls each SkinAsset before\n * `instantiate`.\n */\n readonly skinGuids?: readonly string[];\n}\n\n// === Skeleton / Skin / AnimationClip asset POD shapes (feat-20260523-skin-skeleton-animation M0) ===\n//\n// Decision anchors:\n// - requirements AC-01 (SkeletonAsset shape: kind, guid, inverseBindMatrices Float32Array, jointCount)\n// - requirements AC-04 (Skin sub-asset shape: kind, guid, skeletonGuid, jointPaths)\n// - requirements AC-07 (AnimationClip shape: kind, guid, duration, channels)\n// - plan-strategy D-1 (3-asset separation: IBM/Skin bindings/AnimationClip curves physically independent)\n// - charter P3 (explicit failure: all shape carry typed fields, no loose Record<string,unknown> payloads)\n//\n// AnimationChannel / AnimationSampler sub-types are inline here since they are\n// exclusively consumed by AnimationClip; no other asset or component references them.\n\n/** Stable target identity linking a sampler to a scene entity. */\nexport interface AnimationChannel {\n /** Stable animation target identity. */\n readonly targetId: AnimationTargetIdValue;\n /** Target transform property: 'translation' | 'rotation' | 'scale' | 'weights'. */\n readonly property: 'translation' | 'rotation' | 'scale' | 'weights';\n /** Sampler driving this channel. */\n readonly sampler: AnimationSampler;\n}\n\n/**\n * Animation sampler — keyframe curve for a single animation-target-property pair.\n *\n * `input` and `output` are Float32Arrays of equal length\n * (`output.length = input.length * elementCount`) where elementCount is:\n * - 3 for 'translation' / 'scale' (vec3)\n * - 4 for 'rotation' (quat)\n * - targetCount for 'weights' (morph weights)\n *\n * `interpolation` is restricted to LINEAR and STEP per D-1 scope;\n * CUBICSPLINE is deferred to OOS-skin-cubicspline (fail-fast at importer).\n */\nexport interface AnimationSampler {\n readonly input: Float32Array;\n readonly output: Float32Array;\n readonly interpolation: 'LINEAR' | 'STEP';\n}\n\n/**\n * Animation clip asset POD shape.\n *\n * `duration` is max(sampler.input[last]) across all channels — the\n * longest channel defines the clip length. Each channel targets one\n * animation-target property, resolved during playback by `targetId`.\n */\nexport interface AnimationClip {\n readonly kind: 'animation-clip';\n readonly duration: number;\n readonly channels: readonly AnimationChannel[];\n}\n\n// === AnimationGraph asset POD + node union (feat-20260713 M2 / w13) ==============\n//\n// Decision anchors:\n// - requirements AC-02 (declarative Clip/Blend/Add + nesting graph carried as\n// a shared<AnimationGraph> asset handle, multi-entity shared).\n// - requirements AC-14 (AnimationGraph joins the closed `Asset` union, owns a\n// GUID, and serializes into pack/scene round-trip — foundation landed here,\n// the serialize/deserialize mechanism itself is M4).\n// - requirements OOS-1/OOS-2/OOS-3/OOS-4 (node union is CLOSED at three\n// variants — clip / blend / add. No FSM state/transition, no bone Mask, no\n// built-in transition layer, no BlendSpace fields are reserved; deferred\n// features add nodes in a future closed loop, not speculative fields now —\n// charter F4 \"no unvalidated abstraction\").\n// - requirements OOS-7 (POD carries only the topology of an engine-authored\n// defineAnimationGraph graph; no DCC import metadata).\n// - plan-strategy D-4 (POD + node union land in types/index.ts, the single-file\n// SSOT for every Asset POD, alongside AnimationClip).\n//\n// The POD mirrors AnimationClip: no inline `guid` field — the GUID is assigned by\n// the AssetRegistry / shared-handle system when the graph is registered (like\n// AnimationClip / MaterialAsset / VideoAsset). Nodes are stored flat in `nodes[]`\n// and referenced by index; `root` is the index of the output node. Clip leaves\n// carry a durable GUID string; the runtime consumer resolves that GUID to a\n// World-local transient shared handle only at evaluation time.\n\n/**\n * Clip leaf node — samples a single `shared<AnimationClip>` at the node's\n * runtime seek-time (M3 evaluation). `weight` is the node's STATIC weight; the\n * effective weight is `runtime weight x static weight` (requirements AC-07\n * orthogonal product).\n */\nexport interface AnimationGraphClipNode {\n readonly type: 'clip';\n readonly clip: string;\n readonly weight: number;\n}\n\n/**\n * Blend node — normalizing lerp over its children (requirements AC-04). Child\n * effective weights are normalized so they sum to 1 at evaluation. `children`\n * are indices into the parent {@link AnimationGraph.nodes} array.\n */\nexport interface AnimationGraphBlendNode {\n readonly type: 'blend';\n readonly children: readonly number[];\n readonly weight: number;\n}\n\n/**\n * Add node — non-normalizing additive stack (requirements AC-05). The `base`\n * child contributes its effective weight unchanged; each `additive` layer is\n * added on top WITHOUT normalization (total may exceed 1). `base` and\n * `additive` are indices into {@link AnimationGraph.nodes}.\n */\nexport interface AnimationGraphAddNode {\n readonly type: 'add';\n readonly base: number;\n readonly additive: readonly number[];\n readonly weight: number;\n}\n\n/**\n * Closed node union — exactly three variants (clip / blend / add). AI users\n * exhaustive `switch (node.type)` without default; TS guards completeness\n * (charter P3). No FSM / Mask / transition / BlendSpace variants are reserved\n * (OOS-1..4).\n */\nexport type AnimationGraphNode =\n | AnimationGraphClipNode\n | AnimationGraphBlendNode\n | AnimationGraphAddNode;\n\n/**\n * AnimationGraph asset POD — a Clip/Blend/Add DAG with per-node static weights.\n *\n * Joins the closed `Asset` union with `kind: 'animation-graph'` (AC-14); minted\n * into a `Handle<'AnimationGraph', 'shared'>` via `world.allocSharedRef` /\n * `AssetRegistry`, shared across multiple entities. Constructed via\n * `defineAnimationGraph` (runtime), which validates topology (no out-of-range\n * refs / cycles / invalid weights / empty graph) before a handle is minted.\n */\nexport interface AnimationGraph {\n readonly kind: 'animation-graph';\n readonly nodes: readonly AnimationGraphNode[];\n readonly root: number;\n}\n\n// === VideoAsset POD shape (feat-20260623-world-space-video-asset M1) ==========\n//\n// Decision anchors:\n// - requirements AC-01 (VideoAsset is Asset closed-union 15th member;\n// kind discriminator 'video'; payload { url: string }, no width/height/duration).\n// - requirements constraint: payload must not inline video bytes, only a URL descriptor.\n// - plan-strategy D-4 (VideoAsset descriptor naming aligns with TextureAsset/AudioClipAsset).\n// - charter F1 (AI users discover the schema via IDE autocomplete on the closed\n// `Asset` union + `Handle<VideoAsset>` returns from `AssetRegistry.register`).\n// - plan-strategy D-5 (resolveTexLike identifies video kind via `payload.kind === 'video'`;\n// video does not masquerade as 'texture').\n//\n// `refs` is always empty (isolated leaf) — VideoAsset carries no sub-asset\n// references (plan-strategy S6.3). The `url` field points to an external video\n// file; the runtime resolves it into an HTMLVideoElement via the host-provided\n// `VideoElementProvider` World Resource (plan-strategy D-1).\n\n/**\n * Video asset POD shape -- pure `{url}` descriptor.\n *\n * `VideoAsset` is a runtime-only asset kind (OOS-1: no import/cook pipeline).\n * The `url` field points to an external video file (e.g. `*.webm` / `*.mp4`);\n * the engine does NOT decode video bytes -- it delegates to the host-side\n * `HTMLVideoElement` via `VideoElementProvider` (plan-strategy D-1).\n *\n * `width` / `height` / `duration` are deliberately absent from the POD:\n * the runtime reads them from `HTMLVideoElement.videoWidth` /\n * `videoHeight` / `duration` after `loadedmetadata` fires (requirements\n * constraint \"payload must not inline video bytes\").\n *\n * Consumers reference a VideoAsset via a material texture value\n * fields (e.g. `baseColorTexture`), sharing the same `texture2d` slot with\n * static textures (charter P4 consistent abstraction). The extraction layer\n * (render-system-extract `resolveTexLike`) identifies the video kind and\n * routes to the per-frame transient texture pathway instead of the static\n * `GpuResourceStore.ensureResident` cache (plan-strategy D-5).\n */\nexport interface VideoAsset {\n readonly kind: 'video';\n readonly url: string;\n}\n\n/**\n * Skeleton asset POD shape — pure rig data, no mesh attachment.\n *\n * `inverseBindMatrices` is a Float32Array of length jointCount * 16\n * (column-major mat4 per joint). Missing IBM in source glTF is filled\n * with identity mat4 at importer time.\n *\n * `jointCount` is the number of joints (= IBM array length / 16).\n * Keys off the glTF skin's `joints[]` array length; validated against\n * MAX_JOINTS (256) at importer time.\n */\nexport interface SkeletonAsset {\n readonly kind: 'skeleton';\n readonly inverseBindMatrices: Float32Array;\n readonly jointCount: number;\n}\n\n/**\n * Skin sub-asset — the binding between a skeleton and a scene node hierarchy.\n *\n * `skeletonGuid` references a SkeletonAsset by GUID (string form).\n * `jointPaths` is a parallel array to the skeleton's joints; each entry\n * is a Name-component path from scene root to the joint entity, used\n * at post-spawn time to populate Skin.joints: Entity[].\n *\n * Zero-Entity-reference at the asset layer (AC-06): no Entity or\n * LocalEntityId fields — the binding is name-based, resolved at instantiate time.\n */\nexport interface SkinAsset {\n readonly kind: 'skin';\n readonly skeletonGuid: string;\n readonly jointPaths: readonly string[];\n}\n\n/**\n * VertexAttributeMap — 14-key closed set (feat-20260823 vertex-color asset closure).\n *\n * Canonical interleaved order (plan-strategy F-1, must match bridge + layout layers):\n * position / normal / uv / tangent / skinIndex / skinWeight / uv1..uv7 / color\n *\n * @location mapping per plan-strategy D-4:\n * position@0 normal@1 uv@2 tangent@3 skinIndex@4 skinWeight@5\n * uv1@6 uv2@7 uv3@8 uv4@9 uv5@10 uv6@11 uv7@12 color@13\n *\n * Keys align with Three.js r184 `BufferGeometry.attributes` naming (D-P1 +\n * plan-strategy §7.2 mental migration stance). Importers rename at ingest\n * (`POSITION -> position` / `TEXCOORD_0 -> uv` / `JOINTS_0 -> skinIndex` /\n * `WEIGHTS_0 -> skinWeight`) so the runtime key space remains lowercase.\n *\n * All 14 keys are optional; a mesh with only `position` (static unlit) is\n * valid. Values accept the three common binary shapes:\n * `ArrayBuffer | Float32Array | Uint16Array` (extend only via minor add per\n * the closed-union evolution contract).\n *\n * AC-15 narrowing: consumer sites writing\n * `for (const [key, buffer] of Object.entries(attributes))` observe `key`\n * typed as the 14-member literal union without `as` casts; any typo (e.g.\n * `'POSITION'`) is a TS compile-time error.\n */\nexport interface VertexAttributeMap {\n position?: ArrayBuffer | Float32Array | Uint16Array;\n normal?: ArrayBuffer | Float32Array | Uint16Array;\n uv?: ArrayBuffer | Float32Array | Uint16Array;\n tangent?: ArrayBuffer | Float32Array | Uint16Array;\n skinIndex?: ArrayBuffer | Float32Array | Uint16Array;\n skinWeight?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 1 (feat-20260629-multi-uv-set-support m3-w3, pre-added by M1 for bridge typecheck). */\n uv1?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 2 */\n uv2?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 3 */\n uv3?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 4 */\n uv4?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 5 */\n uv5?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 6 */\n uv6?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 7 */\n uv7?: ArrayBuffer | Float32Array | Uint16Array;\n /** Optional per-vertex linear RGBA color, exactly four finite floats per vertex. */\n color?: Float32Array;\n}\n\n/** Closed storage vocabulary used by the geometry pack boundary. */\nexport type VertexAttributeStorage = 'array-buffer' | 'float32' | 'uint16' | 'other';\n\n/** Lossless detail for one rejected canonical vertex attribute pack. */\nexport type VertexAttributePackDetail =\n | {\n readonly field: 'vertexCount';\n readonly reason: 'vertex-count-invalid';\n readonly actual: number;\n }\n | {\n readonly field: 'attributes';\n readonly reason: 'attributes-empty';\n readonly actualCount: 0;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-storage-invalid';\n readonly expectedStorage: 'float32' | 'uint16';\n readonly actualStorage: VertexAttributeStorage;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-cardinality-mismatch';\n readonly vertexCount: number;\n readonly componentsPerVertex: number;\n readonly expectedLength: number;\n readonly actualLength: number;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-non-finite';\n readonly elementIndex: number;\n readonly actual: 'nan' | 'positive-infinity' | 'negative-infinity';\n };\n\n/**\n * Canonical UV attribute key order (set 0 = `uv`, sets 1..7 = `uv1..uv7`),\n * matching the VertexAttributeMap declaration + @location numbering (D-4).\n * SSOT for \"how many UV sets does this attribute map carry\" so the import,\n * gpu-resource, and layout-derivation layers count identically (no drift).\n */\nexport const UV_ATTRIBUTE_KEYS = ['uv', 'uv1', 'uv2', 'uv3', 'uv4', 'uv5', 'uv6', 'uv7'] as const;\n\n/**\n * Structural input for the UV-set counters: any object that may carry the\n * canonical UV attribute keys. Both the loosely-typed mesh-attribute record\n * (`Record<string, unknown>`) and the closed `VertexAttributeMap` satisfy it,\n * so the import / gpu-resource / layout layers all call one counter.\n */\nexport type UvAttributeSource = Partial<Record<(typeof UV_ATTRIBUTE_KEYS)[number], unknown>>;\n\n/**\n * Total number of UV sets present in an attribute map: the highest populated\n * `uv`/`uv1..uv7` index + 1, or 0 when none are present. A populated key is one\n * whose value is a typed array / array buffer / number array (the binary shapes\n * a vertex attribute can take). This is the single counter all multi-UV layers\n * derive from (feat-20260629 F-4 DRY collapse).\n */\nexport function countUvSets(attrs: UvAttributeSource | undefined): number {\n if (attrs === undefined) return 0;\n for (let i = UV_ATTRIBUTE_KEYS.length - 1; i >= 0; i--) {\n // biome-ignore lint/style/noNonNullAssertion: bounded index on const tuple\n const v = attrs[UV_ATTRIBUTE_KEYS[i]!];\n if (\n v instanceof Float32Array ||\n v instanceof Uint16Array ||\n v instanceof ArrayBuffer ||\n Array.isArray(v)\n ) {\n return i + 1;\n }\n }\n return 0;\n}\n\n/**\n * Extra UV sets beyond set 0 (= `countUvSets - 1`, floored at 0). The\n * gpu-resource + register layers size the dynamic vertex stride from this.\n */\nexport function countExtraUvSets(attrs: UvAttributeSource | undefined): number {\n const total = countUvSets(attrs);\n return total > 0 ? total - 1 : 0;\n}\n\n// === Asset error model SSOT (feat-20260511-asset-system-v1 / D-P1 / w3) =========\n//\n// Decision anchors:\n// - requirements §G3 + AC-03 + AC-10 + AC-21 + §1 callout row 9 (4-member closed\n// `AssetErrorCode` independent from `RhiErrorCode`; `AssetError` class with\n// `.code / .expected / .hint / .message` four-field surface structurally\n// parallel to `RhiError` / `InspectorError` / `MetricError`)\n// - plan-strategy §2 D-P1 (`@forgeax/engine-types` single-file SSOT for\n// AssetErrorCode; independent closed union aligned with\n// MetricErrorCode / InspectorErrorCode precedent)\n// - plan-strategy §7.3 (per-code `.hint` string literals locked verbatim\n// below; any drift updates both this module and the test fixtures)\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — `switch (err.code)` exhaustive without `default:`) +\n// proposition 5 (consistent abstraction — structurally parallel to\n// `@forgeax/engine-rhi` `RhiError` surface)\n// - architecture-principles #1 SSOT (the 4 literals + class shape live here\n// once; M4 AssetRegistry / M3 Geometry factories / AGENTS.md §Error model\n// row all reference this module)\n\n/**\n * Closed `AssetErrorCode` union — 23 members (D-P1 + feat-20260518 D-1 minor\n * evolution + feat-20260520-skylight-ibl-cubemap 5 members +\n * feat-20260523 mesh-upload-fix 1 member +\n * feat-20260523-shader-template-instance-split M1-T02 1 member +\n * feat-20260526-material-asset-multipass-renderstate M1 1 member +\n * feat-20260603-asset-import-loader-injection M1 2 members +\n * feat-20260604-hdr-equirect-cube-importer-loader M2 1 member +\n * feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 3 members +\n * feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 1 member +\n * feat-20260707-texture-block-compression M5 1 member\n * (mipgen-unsupported-compressed-format);\n * requirements §G3 + AC-03 + AC-21 +\n * feat-20260518 AC-02 + bug-20260523 AC-01. The runtime-guard SSOT for the\n * current member count is the ASSET_ERROR_HINTS key-count test, not this prose.)\n * Exhaustive `switch (err.code)` needs no default fallback — TypeScript guards\n * union completeness at compile time (charter F2/P2 machine-readable union >\n * prose + P3 explicit failure).\n *\n * Domain-separated from `RhiErrorCode 'asset-not-registered'` (which is a\n * render-time registry lookup miss, 18-member closed union in\n * `@forgeax/engine-rhi/src/errors.ts`). The two unions cover disjoint\n * lifecycle phases — AI users face only these 22 alternatives on the\n * `assets.load(guid, kind)` / `world.sharedRefs.resolve(handle)` /\n * `assets.installDecoder(kind, decoder)` surface.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'asset-not-found'` | `AssetRegistry.get(handle)` returned no entry (handle never registered or registry was reset); charter P3 explicit failure. |\n * | `'asset-parse-failed'` | decoded bytes are not a valid image (PNG / JPG header corruption on the load path; dimensions <= 0 / segments < 1 on the procedural geometry constructor path — double semantics locked by requirements §9 \"constructor path\" extension). |\n * | `'asset-format-unsupported'` | URL content-type / magic bytes are neither PNG nor JPG (v1 scope; KTX2 / Basis / GLTF embedded textures deferred to M3+). |\n * | `'asset-fetch-failed'` | `fetch(url)` returned non-2xx, threw, or the URL was otherwise unreachable (404 / network / CORS surface). |\n * | `'asset-invalid-value'` | `register<MaterialAsset>(payload)` value validation fails the 3-tier validator (type-mismatch / extra-key / missing-required) — fail-fast at register entry. |\n * | `'cubemap-handle-missing'` | internal equirect-to-cubemap projection has no live cubemap for a `Skylight.equirect` handle; `.hint` points to loading an EquirectAsset + caps.rgba16floatRenderable. |\n * | `'invalid-source-format'` | image importer path when `.hdr` decode needs rgba16float / rgba32float. |\n * | `'load-failed'` | `AssetRegistry.load` when guid entry exists in catalog but file is inaccessible. |\n * | `'device-unsupported'` | GPU capability gate: `device.caps.rgba16floatRenderable` missing. |\n * | `'ibl-precompute-not-dispatched'` | `IblPipelineCache` when counter increments but `queue.submit` hasn't been called. |\n * | `'mesh-vertex-stride-mismatch'` | `register({ kind: 'mesh', ... })` vertices buffer is not evenly divisible by 12 floats per vertex (position vec3 + normal vec3 + uv vec2 + tangent vec4) or `maxIndex+1 !== vertexCount` — fail-fast at register entry (charter P3 structured failure; `.detail` carries `vertexCount` / `floatsPerVertex`). |\n| `'material-circular-inheritance'` | material resolve detected a cycle in the parent chain; `.hint` carries the full cycle path (e.g. \"A -> B -> A\") via `err.detail.cycle`. |\n * | `'loader-not-registered'` | `AssetRegistry.load` dispatched on `asset.kind` but no installed decoder exists for that kind; `.detail.kind` is the missing kind and `.detail.registeredKinds` lists the kinds currently wired. |\n * | `'asset-not-imported'` | `AssetRegistry.load` found the GUID in the catalog but its DDC is absent and no build-time publication exists; `.hint` points back to build-time pre-import rather than a runtime workaround. |\n * | `'texture-source-not-imported'` | `loadTextureAsset` received an uncooked source locator instead of a Pack v2 artifact; the runtime carries no source decoder. |\n */\nexport type AssetErrorCode =\n | 'asset-not-found'\n | 'asset-parse-failed'\n | 'asset-format-unsupported'\n | 'asset-fetch-failed'\n | 'catalog-source-unconfigured'\n | 'asset-invalid-value'\n | 'cubemap-handle-missing'\n | 'invalid-source-format'\n | 'load-failed'\n | 'device-unsupported'\n | 'ibl-precompute-not-dispatched'\n | 'mesh-vertex-stride-mismatch'\n // === 1 new code (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'material-shader-ref-broken'\n // === 1 new code (feat-20260526-material-asset-multipass-renderstate M1 / w6) ===\n | 'material-circular-inheritance'\n // === 2 new codes (feat-20260603-asset-import-loader-injection M1 / w1) ===\n | 'loader-not-registered'\n | 'asset-not-imported'\n // === 1 new code (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===\n | 'texture-source-not-imported'\n // === 1 new code (perf-20260706-raw-container-failfast) ===\n // A mesh/material/scene/skeleton/skin/animation-clip catalog row whose\n // packageUrl is still a raw source container (.glb/.gltf/.fbx), not an\n // importer-produced artifact (.bin/.pack.json). Like texture-source-not-imported\n // this is transport-eligible: the studio form lazily imports via the injected\n // ImportTransport; the shipped form fails fast. Distinct from the generic\n // asset-not-imported so it never masks the parent-missing breadcrumb.\n | 'source-not-imported'\n // === 3 new codes (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===\n | 'mesh-renderer-material-override-invalid'\n | 'mesh-renderer-material-override-overflow'\n | 'mesh-asset-submeshes-empty'\n | 'mesh-asset-material-slot-index-out-of-range'\n | 'mesh-submesh-index-range-out-of-bounds'\n // === 1 new code (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n // Tileset region rectangle out of atlas extent OR tile entry regionIndex out of\n // regions array bounds (single closed code per plan-strategy §D-6 first-error\n // ordering). 19 -> 20 baseline-restored.\n | 'tileset-region-index-out-of-range'\n // === 1 new code (feat-20260629-multi-uv-set-support M2 / m2-w5) ===\n // mesh-bin header v2 contract violation: version unknown, uvSetCount out of\n // [0,8], or stride/floatsPerVertex self-consistency check failed at encode\n // exit or decode entry (Fail Fast). Carries detail { version, uvSetCount,\n // stride }. .hint = 're-cook the asset via importer'.\n | 'mesh-bin-contract-violation'\n // === 1 new code (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===\n // Tile entry optional field (widthCells / heightCells / pivotX / pivotY /\n // collider) or top-level atlases / region.atlasIndex schema invariant\n // breached at register time. `.detail.field` carries the closed 7-variant\n // enum + `.scope?` is 'tile-entry' | 'tileset-asset' (plan-strategy §D-6;\n // charter P3 closed enum + AI-grep affordance). 20 -> 21 M1 net add.\n | 'tileset-tile-entry-malformed'\n // === 1 new code (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===\n | 'asset-invalidated'\n // === 1 new code (feat-20260707-texture-block-compression M5 / w35, D-9) ===\n // deriveRenderDataTexture fail-fast: a block-compressed `format` requested\n // RUNTIME mip generation (`mipmap:true` with no offline `mipLevelCount>1`\n // chain). Compressed formats are not render targets, so the mipmap blit\n // pipeline cannot generate their mips (F-7); the chain must be baked offline.\n // `.hint` carries the self-recovery (bake offline mips, or set the sidecar\n // `compressionMode:'none'`). A compressed texture whose mips are ALREADY in\n // `data` (mipLevelCount>1 from a KTX2 level chain) does NOT trip this gate.\n | 'mipgen-unsupported-compressed-format';\n\n/**\n * Structured asset error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) structurally parallel to `@forgeax/engine-rhi`\n * `RhiError` + `@forgeax/engine-remote` `InspectorError` + `MetricError`\n * (charter proposition 5 consistent abstraction; AGENTS.md \"Errors are\n * structured. Return Result, never throw for expected failures.\").\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'asset-fetch-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter proposition 4 explicit failure\n * red line).\n *\n * The `.message` field is auto-composed for human stack traces and carries\n * the same content as `.code` + `.expected` + `.hint`; AI users prefer\n * field access on the structured triple.\n *\n * @example AI-user exhaustive switch on the 22 members (no default fallback)\n * ```ts\n * import { AssetError, type AssetErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: AssetErrorCode): string {\n * switch (code) {\n * case 'asset-not-found': return 'ensure handle was registered before get()';\n * case 'asset-parse-failed': return 'check file integrity or geometry dimensions';\n * case 'asset-format-unsupported': return 'convert to PNG or JPG';\n * case 'asset-fetch-failed': return 'check url path or dev server';\n * case 'asset-invalid-value': return 'read err.hint / err.detail for the case-specific fix';\n * }\n * }\n * ```\n */\nexport class AssetError extends Error {\n readonly code: AssetErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<AssetErrorDetail>;\n\n constructor(args: {\n code: AssetErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<AssetErrorDetail>;\n }) {\n super(`[AssetError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'AssetError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals (plan-strategy §7.3 lock-in). Exported\n * so M3 Geometry factories / M4 AssetRegistry / tests consume the same\n * SSOT — any drift here updates both producer call sites and the\n * AGENTS.md §Error model table.\n *\n * The shape is a `Record<AssetErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter proposition 4 explicit failure).\n */\nexport const ASSET_ERROR_HINTS: Readonly<Record<AssetErrorCode, string>> = {\n 'asset-fetch-failed':\n 'check url path; verify dev server is running; in tests use data: URL fixture (data:image/png;base64,...)',\n 'catalog-source-unconfigured':\n 'call AssetRegistry.setCatalogSource(source) before enumerateCatalog(), then retry the operation',\n 'asset-parse-failed':\n 'check file bytes are not corrupted; for procedural geometry: verify all dimensions > 0 and segments >= 1',\n 'asset-format-unsupported':\n 'v1 supports png/jpg only; convert .bmp/.webp etc. via image tooling; gltf/glb supported via @forgeax/engine-gltf importer (forgeax-engine-remote-gltf import <gltf-or-glb>)',\n 'asset-not-found':\n 'handle id not in registry; verify register() was called before get(); inspect() returns all live handles',\n 'asset-invalid-value':\n 'a register-time value failed validation; read err.hint for the case-specific fix (e.g. clamp a MaterialAsset param to [0,1], or give a strip-topology MeshAsset an index buffer) and err.detail for the offending field/value',\n 'cubemap-handle-missing':\n 'the equirect-to-cubemap projection (internal to the render-system record arm) has no live cubemap for this Skylight; ensure Skylight.equirect references a loaded EquirectAsset handle and caps.rgba16floatRenderable is true',\n 'invalid-source-format':\n 'decode .hdr via @forgeax/engine-image first; supported formats are rgba16float and rgba32float',\n 'load-failed':\n 'source asset could not be loaded; check GUID validity and file accessibility in the pack-index catalog',\n 'device-unsupported':\n 'GPU device lacks required capability; check device.caps for rgba16float renderable feature',\n 'ibl-precompute-not-dispatched':\n 'check IblPipelineCache.runIblPrecompute is called inside the internal GpuResourceStore equirect-to-cubemap projection; counters must not increment before queue.submit (plan D-7 / N-3 AC-20 invariant)',\n 'mesh-vertex-stride-mismatch':\n 'use meshFromInterleaved (packages/runtime/src/geometry/box.ts) or expand vertices buffer to canonical 12F layout (position vec3 + normal vec3 + uv vec2 + tangent vec4)',\n // === 1 new hint (feat-20260523-shader-template-instance-split M1-T02) ===\n 'material-shader-ref-broken':\n 'the materialShader identifier (path or GUID) resolves to no registered shader; check ShaderRegistry for path identifiers or AssetRegistry for GUID sub-assets',\n // === 1 new hint (feat-20260526-material-asset-multipass-renderstate M1 / w6) ===\n 'material-circular-inheritance':\n 'circular parent chain detected; inspect parent handles — use err.detail.cycle to see the full path (e.g. \"A -> B -> A\")',\n // === 2 new hints (feat-20260603-asset-import-loader-injection M1 / w1) ===\n 'loader-not-registered':\n 'no loader registered for this asset kind; register it via engine.assets.loaders.register(loader) (the loader carries its own kind); err.detail.registeredKinds lists the kinds currently wired',\n 'asset-not-imported':\n 'GUID is in the catalog but its DDC artefact is missing and no ImportTransport is wired (shipped form never falls back to a runtime import); add the asset to the build-time pre-import step instead of importing at runtime',\n // === 1 new hint (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===\n 'texture-source-not-imported':\n 'texture source not imported yet; wire createDevImportTransport() in the studio form for dev lazy-import, or pre-import via the build-time pipeline',\n // === 1 new hint (perf-20260706-raw-container-failfast) ===\n 'source-not-imported':\n 'this mesh/material/scene sub-asset row still points at the raw source container (.glb/.gltf/.fbx); wire createDevImportTransport() for dev lazy-import (POST /__import), or pre-import via the build-time pipeline. The runtime does not parse raw containers at load time.',\n // === 3 new hints (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===\n 'mesh-renderer-material-override-invalid':\n 'a MeshRenderer.materials slot is stale or does not resolve to a MaterialAsset; the renderer inherited the MeshAsset default for that slot',\n 'mesh-renderer-material-override-overflow':\n 'MeshRenderer.materials contains entries beyond MeshAsset.materialSlots; extra overrides are ignored',\n 'mesh-asset-submeshes-empty':\n 'MeshAsset.submeshes must have at least one entry; every mesh must declare at least one submesh; check MeshAsset registration payload for empty submeshes array',\n 'mesh-asset-material-slot-index-out-of-range':\n 'MeshAsset submesh materialSlot must index MeshAsset.materialSlots; re-cook the mesh and inspect the offending submesh/slot topology',\n 'mesh-submesh-index-range-out-of-bounds':\n 'submesh indexOffset + indexCount exceeds the parent mesh index buffer length; check submesh index range bounds against MeshAsset.indices and MeshAsset.vertices; err.detail carries submeshIndex, indexOffset, indexCount, indexBufferLength, and meshAssetGuid',\n // === 1 new hint (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n 'tileset-region-index-out-of-range':\n 'a TilesetAsset.regions[] rectangle escapes the atlas extent OR a TilesetAsset.tiles[].regionIndex points past TilesetAsset.regions.length; check regions[i] (x + width <= atlasWidth, y + height <= atlasHeight) and tiles[i].regionIndex in [0, regions.length); err.detail carries tilesetGuid, tileId, regionIndex, regionCount',\n // === 1 new hint (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===\n 'tileset-tile-entry-malformed':\n 'a TilesetTileEntry optional field is out of range (widthCells / heightCells in (0, 64], pivotX / pivotY in [0, 1], collider rect/polygon in normalized [0,1]^2 with rect.length === 4 and polygon.points.length >= 3) OR a top-level field is out of range (atlases.length >= 1, region.atlasIndex in [0, atlases.length)); engine fail-fast at register-time. read err.detail.field (closed enum) + err.detail.scope (tile-entry | tileset-asset) + err.detail.tileEntryIndex to locate the offending entry; switch (err.detail.field) covers the 7 variants exhaustively without default',\n // === 1 new hint (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===\n 'asset-invalidated':\n 'The asset was invalidated during load; call AssetRegistry.load(guid, kind) again to retry with a fresh fetch',\n // === 1 new hint (feat-20260629-multi-uv-set-support M2 / m2-w5) ===\n 'mesh-bin-contract-violation':\n 're-cook the asset via importer; the .bin sidecar v4 contract is violated — inspect err.detail.reason and its expected/actual projection, stride, cardinality, and byte-length facts',\n // === 1 new hint (feat-20260707-texture-block-compression M5 / w35, D-9) ===\n 'mipgen-unsupported-compressed-format':\n 'compressed-texture mips must be baked offline (the GPU cannot generate mips for a non-render-target block format); re-cook with an offline mip chain, or set the sidecar .meta.json compressionMode:\"none\" (or mipmap:false) to keep runtime mip generation on an uncompressed texture',\n};\n\n// === Font error model SSOT (feat-20260531-world-space-msdf-text-rendering M2 / w6) ===\n//\n// Decision anchors:\n// - plan-strategy D-11 (two closed unions: FontErrorCode = build/load phase,\n// TextErrorCode = runtime layout phase; structured .code/.expected/.hint/.detail;\n// TOFU is rendering behaviour not an error — AC-14)\n// - requirements AC-15 (non-TTF -> FontErrorCode 'unsupported-font-format')\n// - requirements AC-16 (both unions in types/src/index.ts; exhaustive\n// switch(err.code) without default compiles)\n// - requirements AC-20 (font concurrency > 8 -> TextErrorCode\n// 'font-concurrency-exceeded')\n// - charter P3 (explicit failure: structured error > silent behaviour,\n// D-8 rejects silent LRU eviction for concurrency violation)\n//\n// Domain separation: FontErrorCode covers build-time bake failures and\n// load-time atlas/sampler resolution; TextErrorCode covers runtime glyph\n// layout and text rendering failures.\n\n/**\n * Closed `FontErrorCode` union — build-time bake + load-time resolution\n * errors (plan-strategy D-11).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'unsupported-font-format'` | bake receives non-TTF input (OTF / WOFF2); `.expected: 'ttf'` (AC-15) |\n * | `'font-atlas-missing'` | typed font load has missing/empty atlas texture GUID |\n * | `'font-atlas-corrupted'` | sidecar JSON parse failed or glyph metrics shape invalid |\n * | `'bake-failed'` | @zappar/msdf-generator call threw (wasm unavailable / internal error) |\n */\nexport type FontErrorCode =\n | 'unsupported-font-format'\n | 'font-atlas-missing'\n | 'font-atlas-corrupted'\n | 'bake-failed';\n\n/**\n * Closed `TextErrorCode` union — runtime glyph layout and text rendering\n * errors (plan-strategy D-11).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'font-concurrency-exceeded'` | > 8 distinct FontAsset handles active in one frame; `.expected: 8` (AC-20) |\n * | `'font-atlas-missing'` | glyph layout system resolved fontHandle but atlas texture is not yet uploaded |\n * | `'glyph-layout-failed'` | layout computation encountered unexpected state (empty common block, etc.) |\n */\nexport type TextErrorCode =\n | 'font-concurrency-exceeded'\n | 'font-atlas-missing'\n | 'glyph-layout-failed';\n\n/**\n * Structured font error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) in the style of {@link AssetError}.\n *\n * AI users consume via property access:\n * `switch (err.code) { case 'unsupported-font-format': ... err.expected ... }`\n * — never by parsing `.message`.\n */\nexport class FontError extends Error {\n readonly code: FontErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<Record<string, unknown>>;\n\n constructor(args: {\n code: FontErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<Record<string, unknown>>;\n }) {\n super(`[FontError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'FontError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Structured text error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) in the style of {@link AssetError}.\n *\n * AI users consume via property access:\n * `switch (err.code) { case 'font-concurrency-exceeded': ... err.hint ... }`\n * — never by parsing `.message`.\n */\nexport class TextError extends Error {\n readonly code: TextErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<Record<string, unknown>>;\n\n constructor(args: {\n code: TextErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<Record<string, unknown>>;\n }) {\n super(`[TextError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'TextError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n// === AssetErrorDetail discriminated union (feat-20260523-shader-template-instance-split M1-T02) ===\n//\n// Introduced to type-narrow the AssetError.detail field for the new\n// 'material-shader-ref-broken' variant. Existing AssetErrorCode members\n// keep their Record<string, unknown> detail shapes; the union is\n// backward-compatible because the detail field on AssetError is optional.\n\n/**\n * Detail for `material-shader-ref-broken` — materialShader identifier\n * (path or GUID) could not be resolved to a registered shader.\n */\nexport interface AssetMaterialShaderRefBrokenDetail {\n readonly code: 'material-shader-ref-broken';\n readonly materialAssetGuid: string;\n readonly missingShaderId: string;\n readonly materialShaderPath?: string;\n}\n\n/**\n * Detail for `tileset-region-index-out-of-range` (feat-20260608 M0 baseline rebuild).\n *\n * Carries the offending tileset GUID + tile-entry index + the rejected\n * `regionIndex` + the live `regionCount` so AI consumers can pinpoint\n * the malformed payload field via property access (charter P3 / P4).\n *\n * Surfaced by `validateTilesetPayload` along two paths:\n * - region rectangle escapes the parent atlas extent\n * (regionIndex == the offending rectangle index).\n * - `tiles[i].regionIndex` >= `regions.length` (or negative)\n * (tileId encodes which entry; regionIndex carries the rejected value).\n */\nexport interface AssetTilesetRegionIndexOutOfRangeDetail {\n readonly code: 'tileset-region-index-out-of-range';\n readonly tilesetGuid: string;\n readonly tileId: number;\n readonly regionIndex: number;\n readonly regionCount: number;\n}\n\n/**\n * Detail for `tileset-tile-entry-malformed` (feat-20260608 M1 schema\n * extension; plan-strategy §D-6).\n *\n * Closed 7-variant `.field` enum locks the AI-grep affordance: switch\n * (detail.field) over the union compiles without default (charter P3).\n *\n * - `widthCells` -- `tiles[i].widthCells` out of `(0, 64]`.\n * - `heightCells` -- `tiles[i].heightCells` out of `(0, 64]`.\n * - `pivotX` -- `tiles[i].pivotX` out of `[0, 1]`.\n * - `pivotY` -- `tiles[i].pivotY` out of `[0, 1]`.\n * - `collider` -- `tiles[i].collider` schema invariant (rect.length !==\n * 4 / rect dimension out of `[0, 1]^2` / polygon.points.length < 3 /\n * any point out of `[0, 1]^2` / type discriminator outside the closed\n * 3-variant enum).\n * - `atlases` -- top-level `atlases.length < 1` (empty atlas list).\n * - `atlasIndex` -- `regions[i].atlasIndex` outside `[0, atlases.length)`.\n *\n * `.scope?` is `'tile-entry'` when the violation is in `tiles[i].*` (in\n * which case `.tileEntryIndex` carries the offending `tiles[]` index) and\n * `'tileset-asset'` when the violation is at the top level (atlases /\n * region atlasIndex).\n */\nexport interface AssetTilesetTileEntryMalformedDetail {\n readonly code: 'tileset-tile-entry-malformed';\n readonly field:\n | 'widthCells'\n | 'heightCells'\n | 'pivotX'\n | 'pivotY'\n | 'collider'\n | 'atlases'\n | 'atlasIndex';\n readonly scope?: 'tileset-asset' | 'tile-entry';\n readonly tileEntryIndex?: number;\n readonly tilesetGuid: string;\n readonly expected?: string;\n readonly hint?: string;\n}\n\n/**\n * Detail for `mesh-bin-contract-violation`.\n *\n * The mesh-bin header is a projection of the canonical geometry layout. Keep\n * the complete wire facts in the structured detail so recovery never depends\n * on parsing the human-facing `expected` / `actual` strings. `sourceKey` and\n * `reason` identify the owning payload and the failed invariant; the nested\n * snapshots preserve the lossless expected/actual cardinality facts.\n */\nexport type AssetMeshBinContractViolationReason =\n | 'header-truncated'\n | 'version-unsupported'\n | 'header-invalid'\n | 'projection-mismatch'\n | 'payload-length-mismatch'\n | 'metadata-invalid'\n | 'attribute-invalid'\n | 'payload-non-finite';\n\nexport interface AssetMeshBinContractFacts {\n readonly field?:\n | 'byteLength'\n | 'version'\n | 'projectionVersion'\n | 'mask'\n | 'stride'\n | 'digest'\n | 'vertexBytes'\n | 'indexBytes'\n | 'jsonBytes'\n | 'metadata'\n | 'attribute';\n readonly attribute?: keyof VertexAttributeMap;\n readonly elementIndex?: number;\n readonly expectedLength?: number;\n readonly actualLength?: number;\n readonly actualValue?: 'nan' | 'positive-infinity' | 'negative-infinity';\n readonly version?: number;\n readonly projectionVersion?: number;\n readonly mask?: number;\n readonly digest?: string;\n readonly stride?: number;\n readonly vertexCount?: number;\n readonly vertexBytes?: number;\n readonly indexCount?: number;\n readonly indexWidth?: number;\n readonly indexBytes?: number;\n readonly jsonBytes?: number;\n readonly byteLength?: number;\n}\n\nexport interface AssetMeshBinContractViolationDetail {\n readonly code: 'mesh-bin-contract-violation';\n readonly sourceKey: string;\n readonly reason: AssetMeshBinContractViolationReason;\n readonly expected: Readonly<AssetMeshBinContractFacts>;\n readonly actual: Readonly<AssetMeshBinContractFacts>;\n}\n\n/**\n * Discriminated detail union for AssetError, narrowed per AssetErrorCode.\n *\n * Variants:\n * - `material-shader-ref-broken` -- materialShader identifier (path or GUID)\n * could not be resolved to a registered shader; carries materialAssetGuid +\n * missingShaderId.\n * - `asset-invalid-value` -- a register-time value failed validation;\n * carries `{ field: string; got: unknown }`.\n * - `mesh-renderer-material-override-overflow` -- override entries exceed\n * slot count; carries `{ expectedCount, actualCount, meshAssetGuid }`.\n * - `mesh-asset-submeshes-empty` -- MeshAsset.submeshes is empty array;\n * carries `{ meshAssetGuid }`.\n * - `mesh-submesh-index-range-out-of-bounds` -- submesh index range exceeds\n * parent mesh index buffer; carries `{ submeshIndex, indexOffset,\n * indexCount, indexBufferLength, meshAssetGuid }`.\n */\nexport type AssetErrorDetail =\n | import('./asset.js').AssetCodecFailureDetail\n | AssetMaterialShaderRefBrokenDetail\n | AssetTilesetRegionIndexOutOfRangeDetail\n | AssetTilesetTileEntryMalformedDetail\n | AssetMeshBinContractViolationDetail\n | VertexAttributePackDetail\n | { readonly field: string; readonly got: unknown }\n | { readonly field: string; readonly value: unknown; readonly reason: string }\n | { readonly expectedCount: number; readonly actualCount: number; readonly meshAssetGuid: string }\n | { readonly meshAssetGuid: string; readonly slotIndex: number; readonly handle: number }\n | {\n readonly meshAssetGuid: string;\n readonly slotIndex: number;\n readonly slotName: string;\n readonly defaultMaterialGuid: string;\n readonly actualKind: string;\n }\n | MeshMaterialOverrideConflict\n | {\n readonly meshAssetGuid: string;\n readonly submeshIndex: number;\n readonly materialSlot: number;\n readonly materialSlotCount: number;\n }\n | { readonly meshAssetGuid: string }\n | {\n readonly submeshIndex: number;\n readonly indexOffset: number;\n readonly indexCount: number;\n readonly indexBufferLength: number;\n readonly meshAssetGuid: string;\n }\n // Pre-existing detail shapes used by AssetRegistry / loaders / pipeline-builder\n // (added in M5 / w27 alongside the count-mismatch tightening so the union\n // accommodates every current call site without losing structural narrowing).\n | { readonly sourcePath: string }\n | { readonly kind: string; readonly registeredKinds?: readonly string[] }\n | { readonly key: string; readonly legalPattern: string }\n | { readonly passCount: number }\n | {\n readonly passIndex: number;\n readonly shaderKey: string;\n readonly cause: string;\n }\n | { readonly paramName: string; readonly expectedType: string; readonly got: unknown }\n | { readonly paramName: string; readonly got: unknown }\n | { readonly missingParams: readonly string[] }\n | { readonly cycle: string }\n | {\n readonly localId: number;\n readonly component: string;\n readonly field: string;\n readonly index: number;\n readonly refsLength: number;\n }\n | { readonly vertexCount: number; readonly floatsPerVertex: number }\n // feat-20260622 verify r1: sub-asset load-failure breadcrumb in structured\n // form. The recursive loader composes the same provenance into the `.hint`\n // string; this variant additionally exposes it for property access so AI\n // users locate the broken edge without parsing the hint (charter P3,\n // requirements section error-self-recovery). `sourceField`/`sceneEntityId`\n // mirror the originating AssetRef edge; both undefined for transitive\n // (texture) edges with no per-entity origin (D-2).\n | {\n readonly referencedByGuid: string;\n readonly referencedByKind: string;\n readonly subAssetGuid: string;\n readonly sceneEntityId?: number;\n readonly sourceField?: {\n readonly componentName?: string;\n readonly fieldName: string;\n readonly arrayIndex?: number;\n };\n };\n\n// === Image importer error model SSOT ===\n// ImageErrorDetailByCode owns the closed vocabulary and payload shapes. The\n// envelope and runtime constructors are derived from it so `.code` and\n// `.detail` stay correlated for consumers.\n\n/** Closed code union, derived from the detail map below. */\ninterface ImageErrorDetailByCode {\n 'image-decode-failed': {\n readonly reason: string;\n readonly path?: string;\n };\n 'image-format-unsupported': {\n readonly actualMime: string;\n readonly path?: string;\n readonly formatColorSpaceConflict?: {\n readonly format: string;\n readonly colorSpace: 'srgb' | 'linear';\n readonly expected: 'srgb' | 'linear';\n };\n };\n 'image-dimension-out-of-bounds': {\n readonly requested: { readonly width: number; readonly height: number };\n readonly limit: number;\n };\n 'image-meta-missing': {\n readonly sourcePath: string;\n readonly expectedSidecarPath: string;\n };\n 'image-hdr-decode-failed': {\n readonly reason: string;\n readonly path?: string;\n };\n 'atlas-empty-input': {\n readonly receivedCount: number;\n };\n 'atlas-size-exceeded': {\n readonly name: string;\n readonly width: number;\n readonly height: number;\n readonly maxAtlasSize: number;\n };\n 'atlas-region-mismatch': {\n readonly name: string;\n readonly regionsTotalPixels: number;\n readonly atlasPixels: number;\n };\n}\n\nexport type ImageErrorCode = keyof ImageErrorDetailByCode;\n\n/** Detail union projected from one code-to-payload map. */\nexport type ImageErrorDetailFor<C extends ImageErrorCode> = Readonly<{ code: C }> &\n ImageErrorDetailByCode[C];\n\nexport type ImageErrorDetail = {\n [C in ImageErrorCode]: ImageErrorDetailFor<C>;\n}[ImageErrorCode];\n\n/**\n * Correlated error envelope. `ImageErrorFor<C>` is the producer-facing\n * generic; `ImageError` is its closed union for exhaustive consumer switches.\n */\nexport type ImageErrorFor<C extends ImageErrorCode> = Error & {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n};\n\nexport type ImageError = {\n [C in ImageErrorCode]: ImageErrorFor<C>;\n}[ImageErrorCode];\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy section 2.3 / Tier 2\n * documentation). `Record<ImageErrorCode, string>` ensures compile-time\n * completeness; any future minor add to `ImageErrorCode` raises a TS error\n * on this map until the matching hint is supplied (charter proposition 4\n * explicit failure -- producer/consumer + reviewer all see the missing arm).\n *\n * Each hint embeds an executable command so AI users self-recover by\n * copy-pasting the hint into the shell (plan-strategy Tier 2 \"hint must\n * carry forgeax-engine-remote-image import <path>\" or similar; the image\n * plugin bin lands in feat-future-console-plugin-image — until then the\n * hint references the in-package importer surface).\n */\nexport const IMAGE_ERROR_HINTS: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed':\n 'check file integrity; re-export from DCC tool (Photoshop / GIMP / Aseprite); dimensions > 0 + valid PNG / JPG header bytes',\n 'image-format-unsupported':\n 'supports PNG / JPG / TGA true-color sources; convert unsupported formats with: magick convert <input> <output>.png; check importSettings.colorSpace consistency with format family if formatColorSpaceConflict present',\n 'image-dimension-out-of-bounds':\n 'downscale source under device caps (typical maxTextureDimension2D = 8192 / 16384); use mipmap chain instead of larger source if lod is the goal',\n 'image-meta-missing': 'run: forgeax-engine-remote-asset import <path>',\n 'image-hdr-decode-failed':\n 'check .hdr file integrity; verify Radiance RGBE header magic (#?RADIANCE) and FORMAT=32-bit_rle_rgbe header field; ensure file was not truncated',\n // feat-20260521-sprite-atlas-animation M1 T-02 — atlas hook hint strings\n // (plan-strategy section 2 D-2). Each hint embeds an executable recovery\n // path so AI users self-repair by copy-pasting the hint into the shell\n // or into the build config (charter P3 explicit failure + AGENTS.md\n // Error model \"hint must carry executable recovery\").\n 'atlas-empty-input':\n 'verify forgeax-engine-remote-asset atlas --input <glob> --name <prefix> --output <dir> matches at least 1 PNG on disk; run `ls <glob>` to inspect the resolved file set; add the missing sprite source or fix the glob pattern',\n 'atlas-size-exceeded':\n 'downscale the source PNG so width * height <= maxAtlasSize^2 (default 4096); or split sprites across multiple atlas runs (forgeax-engine-remote-asset atlas --input <subset-glob> --name <other-prefix> --output <dir>); or raise the cap via --max-atlas-size 8192 if device caps allow it',\n 'atlas-region-mismatch':\n 'shelfPack returned regions exceeding atlas footprint — packer safety net; file a forgeax-engine bug; rerun forgeax-engine-remote-asset atlas with a smaller input set or lower --max-atlas-size as temporary recovery',\n};\n\n/**\n * Image color-space discriminator (plan-strategy section 2.5 D Open Q-4 (c)).\n *\n * `'srgb'` -- baseColor / albedo authored in sRGB display space; uploaded\n * with `format='*-srgb'` so hardware applies the gamma decode automatically\n * (research F-3 spec guarantee for mipmap blits).\n *\n * `'linear'` -- normal / metallic / roughness / data textures authored in\n * linear color space; uploaded with `format='*-unorm'` (no gamma transform).\n */\nexport type ImageColorSpace = 'srgb' | 'linear';\n\n/**\n * Image importer settings POD (plan-strategy section 2.2 D-4 image disk\n * schema; AC-26). 5-field free-form object persisted into the `*.meta.json`\n * `importSettings` field; the GUID lives at the top of the POD so consumers\n * (importer + runtime + console asset import) share one schema (charter\n * proposition 5 consistent abstraction).\n *\n * Fields:\n * - `guid` -- string-form RFC 4122 dash-form UUID identifying the single\n * image sub-asset (image disk schema is currently single-sub-asset by\n * design; cubemap face / array layer reserved for future feat).\n * - `colorSpace` -- `'srgb' | 'linear'` (drives uploadTexture format\n * selection; plan-strategy section 2.5).\n * - `mipmap` -- `'auto' | 'none'` (`'auto'` enables runtime mipmap-generator\n * blit chain; `'none'` ships a single mip level).\n * - `addressMode` -- WGPU address mode for sampler (passed through to\n * uploadTexture's sampler descriptor).\n * - `filterMode` -- magFilter / minFilter selector.\n *\n * The shape stays free-form `Record<string, unknown>` compatible at the\n * `*.meta.json` `importSettings` slot (research F-9 -- meta.schema.json\n * does not lock importSettings sub-shape; minor add of new fields is\n * non-breaking; plan-strategy R5 risk-free).\n */\nexport interface ImageMeta {\n readonly guid: string;\n readonly colorSpace: ImageColorSpace;\n readonly mipmap: 'auto' | 'none';\n readonly addressMode: 'repeat' | 'clamp-to-edge' | 'mirror-repeat';\n readonly filterMode: 'nearest' | 'linear';\n /** Optional asset-owned cooked-payload target dimension. */\n readonly downscaleMaxDimension?: number;\n}\n\n/**\n * Decoded image POD (plan-strategy section 2.2 + section 3.3; AC-26).\n * Producer: `@forgeax/engine-image` parseImage / decodeImageFromFile.\n * Consumer: `@forgeax/engine-runtime` AssetRegistry.uploadTexture (M3).\n *\n * Six-field tight-packed shape:\n * - `bytes` -- decoded pixel buffer (RGBA tight-packed; 4 bytes per pixel).\n * Producer guarantees `bytes.length === width * height * 4`.\n * - `width` / `height` -- pixel dimensions (must satisfy device caps\n * `maxTextureDimension2D` floor; surfaced as `image-dimension-out-of-bounds`\n * when exceeded).\n * - `mime` -- discriminator over the supported set (`'image/jpeg' | 'image/png' | 'image/x-tga'`);\n * keeps the runtime side from sniffing magic bytes.\n * - `colorSpace` -- carried over from `ImageMeta.colorSpace`; uploadTexture\n * asserts `format <-> colorSpace` consistency at the GPU upload entry\n * (plan-strategy section 2.5 D Open Q-4 (c)).\n * - `mipmap` -- boolean derived from `ImageMeta.mipmap === 'auto'`; the\n * runtime mipmap-generator skips the blit chain when false.\n *\n * Math-free POD; no Float32Array / branded handle on this shape (charter\n * proposition 5 consistent abstraction with TextureAsset POD).\n */\nexport interface DecodedImage {\n readonly bytes: Uint8Array;\n readonly width: number;\n readonly height: number;\n readonly mime: 'image/jpeg' | 'image/png' | 'image/x-tga';\n readonly colorSpace: ImageColorSpace;\n readonly mipmap: boolean;\n}\n\n// === AssetGuid — disk-layer GUID brand type (feat-20260513-guid-asset-package-system) ===========\n//\n// Type-only declaration. Implementation (parse / format / equals / random) lives in\n// @forgeax/engine-pack/guid. The brand field is a phantom string literal that prevents\n// accidental assignment from plain Uint8Array or string at compile time.\n\n/** 16-byte UUID brand for disk-layer asset identification. RFC 4122 UUIDv7 wire form. */\nexport type AssetGuid = Uint8Array & { readonly __guidBrand: 'AssetGuid' };\n\n// === PackErrorCode / PackErrorDetail — disk-layer error SSOT (feat-20260513-guid-asset-package-system w15) ===\n//\n// Decision anchors:\n// - requirements §6.1 (13-member closed union literal set SSOT; widened from\n// the original 8 by feat-20260523-shader-template-instance-split (+1) and\n// feat-20260608-scene-nesting-ecs-fication M1 / w8 (+4))\n// - requirements §6.2 AC-05/07 (per-code discriminated detail)\n// - plan-strategy §D-5 (PackError 4-field surface + check-pack-error-detail-narrowed.mjs guard)\n// - AGENTS.md §Error model (structurally parallel to AssetErrorCode / InspectorErrorCode)\n\n/**\n * Closed PackErrorCode union — 15 members.\n * Used exclusively by the @forgeax/engine-pack scanner fail-fast chain.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'pack-malformed-meta'` | .meta.json fails ajv schema validation |\n * | `'pack-malformed-pack'` | .pack.json fails ajv schema validation |\n * | `'pack-guid-malformed'` | a GUID field is not a valid 36-char RFC 4122 dash-form string |\n * | `'pack-orphan-meta'` | .meta.json exists but the corresponding source file does not |\n * | `'pack-meta-missing'` | source file exists but no .meta.json (strict mode) |\n * | `'pack-guid-collision'` | two .pack.json files declare the same GUID |\n * | `'pack-cyclic-reference'` | asset refs[] (incl. mount.source) form a cycle |\n * | `'pack-subasset-index-out-of-range'` | subAsset.sourceIndex >= source count |\n * | `'payload-schema-mismatch'` | material payload fails materialShader / paramSchema schema |\n * | `'pack-mount-localid-overlap'` | mount memberFirst windows overlap or collide with entities[] |\n * | `'pack-mount-count-mismatch'` | mount memberCount disagrees with referenced child SceneAsset |\n * | `'pack-mount-override-localid-out-of-range'` | override.localId outside the mount's member window |\n * | `'pack-mount-override-unknown-field'` | override.comp / override.field unknown to the schema vocab |\n * | `'pack-unknown-path'` | @name references a name not declared in package.json#forgeax.assets.paths |\n * | `'pack-malformed-path-ref'` | source starts with @ but does not match @<name>/<rest> format, or resolves to a path outside the declared directory |\n *\n * Membership history: 8 -> 9 added 'payload-schema-mismatch' (feat-20260523\n * shader-template-instance-split); 9 -> 13 adds the four mount-* codes\n * (feat-20260608-scene-nesting-ecs-fication M1 / w8, plan-strategy D-8 literals\n * locked); 13 -> 15 adds 'pack-unknown-path' + 'pack-malformed-path-ref'\n * (feat-20260625-asset-meta-source-mount-prefix M1 / w1).\n */\nexport type PackErrorCode =\n | 'pack-malformed-meta'\n | 'pack-malformed-pack'\n | 'pack-guid-malformed'\n | 'pack-orphan-meta'\n | 'pack-meta-missing'\n | 'pack-guid-collision'\n | 'pack-cyclic-reference'\n | 'pack-subasset-index-out-of-range'\n // === 1 new code (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'payload-schema-mismatch'\n // === 4 new codes (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8 literals locked) ===\n | 'pack-mount-localid-overlap'\n | 'pack-mount-count-mismatch'\n | 'pack-mount-override-localid-out-of-range'\n | 'pack-mount-override-unknown-field'\n // === 2 new codes (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n | 'pack-unknown-path'\n | 'pack-malformed-path-ref';\n\n/**\n * Discriminated detail union for PackError — narrowed per PackError.code.\n * AI users access `err.detail.<field>` directly after switch (err.code) narrows\n * the variant. Variants without an own `code` field are narrowed exclusively by\n * the top-level `PackError.code` discriminant (the legacy 7 variants below);\n * variants that carry a `code` field (`payload-schema-mismatch`, the evolved\n * `pack-cyclic-reference`, and the four mount-* additions) double-narrow via\n * `Extract<PackErrorDetail, { code: ... }>` at the type layer (R10).\n *\n * Structurally parallel to RhiErrorDetail / MetricErrorDetail.\n */\nexport type PackErrorDetail =\n | {\n /** Absolute or relative path to the malformed .meta.json file. */\n readonly path: string;\n /** ajv validation errors produced by validateMeta(). */\n readonly ajvErrors: readonly { readonly instancePath: string; readonly message: string }[];\n }\n | {\n /** Absolute or relative path to the malformed .pack.json file. */\n readonly path: string;\n /** ajv validation errors produced by validatePack(). */\n readonly ajvErrors: readonly { readonly instancePath: string; readonly message: string }[];\n /**\n * Optional human-readable reason category. Set to a fixed literal for\n * the runtime instantiate-path SceneEntity field-name typo route:\n * `'unknown component field'` (requirements §AC-08(b)). Absent on\n * scanner-path ajv-validation failures (where the structural\n * ajvErrors[].message string already carries the diagnostic).\n */\n readonly reason?: string;\n }\n | {\n /** The raw string value that failed UUID validation. */\n readonly raw: string;\n /** Human-readable reason (e.g. 'expected 36-char RFC 4122 dash-form UUID'). */\n readonly reason: string;\n }\n | {\n /** Path of the .meta.json that has no corresponding source file. */\n readonly metaPath: string;\n /** Path that was expected to exist as a source file. */\n readonly expectedFile: string;\n }\n | {\n /** Path of the source file that has no accompanying .meta.json. */\n readonly filePath: string;\n }\n | {\n /** The two .pack.json paths that both declare the same GUID (tuple, always length 2). */\n readonly paths: readonly [string, string];\n /** The colliding GUID dash-form string. */\n readonly guid: string;\n }\n // === Evolved variant (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy R10): pack-cyclic-reference now carries `code` + `kind` so\n // the build-time scanner (kind: 'mount-asset', cycle: GUID list) and the\n // runtime ChildOf detector (kind: 'childof', cycle: LocalEntityId list) stay\n // narrowable from a single error code. ===\n | {\n readonly code: 'pack-cyclic-reference';\n /**\n * Cycle origin tag: 'childof' for runtime ChildOf relationship cycles\n * (LocalEntityId stringified), 'mount-asset' for build-time\n * SceneAsset.mounts[].source GUID cycles (D-1).\n */\n readonly kind: 'childof' | 'mount-asset';\n /** Cycle path as ordered identifier strings; first === last. */\n readonly cycle: readonly string[];\n }\n | {\n /** Path of the .meta.json declaring the out-of-range sourceIndex. */\n readonly metaPath: string;\n /** The declared sourceIndex value. */\n readonly sourceIndex: number;\n /** The maximum valid sourceIndex (exclusive upper bound = source count). */\n readonly max: number;\n }\n // === 1 new variant (feat-20260523-shader-template-instance-split M1-T02) ===\n | {\n /** Discriminated code for material payload schema mismatch. */\n readonly code: 'payload-schema-mismatch';\n /** GUID of the offending material asset. */\n readonly guid: string;\n /** ajv validation errors for the material payload. */\n readonly errors: readonly { readonly instancePath: string; readonly message: string }[];\n }\n // === 4 new variants (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8 literals locked; AC-04 / AC-05 / AC-06 / AC-07) ===\n | {\n readonly code: 'pack-mount-localid-overlap';\n /** Overlapping LocalEntityId values (sorted ascending). */\n readonly overlapping: readonly number[];\n /**\n * Source labels for the conflicting windows. Each entry is a\n * human-readable origin string (`mount[<localId>]`,\n * `entities[<localId>]`, etc.) of length matching `overlapping[]`.\n */\n readonly sources: readonly string[];\n }\n | {\n readonly code: 'pack-mount-count-mismatch';\n /** localId of the offending mount within its parent SceneAsset. */\n readonly mountLocalId: number;\n /** memberCount declared on the mount. */\n readonly declared: number;\n /** Actual entities[].length resolved from the referenced child SceneAsset. */\n readonly actual: number;\n }\n | {\n readonly code: 'pack-mount-override-localid-out-of-range';\n /** override.localId that fell outside the mount's member window. */\n readonly overrideLocalId: number;\n /** localId of the parent mount. */\n readonly mountLocalId: number;\n /** memberCount of the parent mount (window upper bound, exclusive). */\n readonly memberCount: number;\n }\n | {\n readonly code: 'pack-mount-override-unknown-field';\n /** Component name on which the override was authored. */\n readonly comp: string;\n /** Unknown field name. */\n readonly field: string;\n /** localId of the parent mount carrying the override. */\n readonly mountLocalId: number;\n }\n // === 2 new variants (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n | {\n readonly code: 'pack-unknown-path';\n /** The @name that was not found in package.json#forgeax.assets.paths. */\n readonly pathName: string;\n /** All known path names declared in package.json#forgeax.assets.paths. */\n readonly knownNames: readonly string[];\n }\n | {\n readonly code: 'pack-malformed-path-ref';\n /**\n * Which malformation occurred, so an AI user can branch on a property\n * instead of parsing the human-facing .hint:\n * - 'format': source starts with @ but does not match @<name>/<rest>\n * - 'escape': rest segment resolves outside the declared path directory\n */\n readonly reason: 'format' | 'escape';\n /** The raw source string as written in the .meta.json. */\n readonly rawSource: string;\n /** The expected format description for self-correction. */\n readonly expectedFormat: string;\n };\n\n/**\n * Per-code .hint string literals SSOT.\n * Record<PackErrorCode, string> ensures compile-time completeness.\n */\nexport const PACK_ERROR_HINTS: Readonly<Record<PackErrorCode, string>> = {\n 'pack-malformed-meta':\n 'check guid is a valid RFC 4122 UUID; validate with: ajv validate -s schema/meta.schema.json -d <file>',\n 'pack-malformed-pack':\n 'check all asset guid and refs[] fields are 36-char dash-form UUIDs; validate with pack.schema.json',\n 'pack-guid-malformed':\n 'use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form',\n 'pack-orphan-meta': 'remove the orphan .meta.json or add the missing source file next to it',\n 'pack-meta-missing':\n 'run forgeax-engine-remote-asset scan --roots <dir> to list source files without .meta.json',\n 'pack-guid-collision':\n 'run forgeax-engine-remote-asset verify to list all GUID collisions; each GUID must be globally unique',\n 'pack-cyclic-reference':\n 'run forgeax-engine-remote-asset verify to print the cycle path; break the cycle by removing a refs[] entry',\n 'pack-subasset-index-out-of-range':\n 'check subAssets[].sourceIndex does not exceed the actual sub-image count in the source file',\n // === 1 new hint (feat-20260523-shader-template-instance-split M1-T02) ===\n 'payload-schema-mismatch':\n 'material asset payload failed schema validation; check paramSchema entries all use valid types from MATERIAL_PARAM_TYPES and materialShader is a non-empty string',\n // === 4 new hints (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8) ===\n 'pack-mount-localid-overlap':\n 'check parent SceneAsset.mounts[].memberFirst windows do not overlap with each other or with entities[].localId; rebuild mount sidecar after the child SceneAsset reimport',\n 'pack-mount-count-mismatch':\n 'mount.memberCount must equal the referenced child SceneAsset totalSlots (entities.length + sum(mounts[].memberCount) + mounts.length); rebuild mount sidecar via forgeax-engine-remote-asset verify <dir> after the child SceneAsset reimport',\n 'pack-mount-override-localid-out-of-range':\n 'override.localId must be in [0, mount.memberCount); shrink the override or extend memberCount to match the child SceneAsset',\n 'pack-mount-override-unknown-field':\n 'override.comp / override.field must match a defined component schema; check defineComponent registry or rebuild mount sidecar after the child SceneAsset reimport',\n // === 2 new hints (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n 'pack-unknown-path':\n 'the @name in source is not declared in package.json#forgeax.assets.paths; add it there or use a known name from the list in error.detail.knownNames',\n 'pack-malformed-path-ref':\n 'source must be @<name>/<rest> where name is a key in package.json#forgeax.assets.paths; the resolved path must not escape the declared directory',\n};\n\n// === AudioErrorCode / AudioError / AudioErrorDetail -- audio error SSOT (feat-20260527-audio-system M1 / w4) ===\n//\n// Decision anchors:\n// - requirements S-8 (5-member independent closed union: context-creation-failed /\n// decode-failed / context-suspended / invalid-clip-handle / bus-not-found)\n// - requirements AC-13 (AudioErrorCode closed union switch exhaustiveness)\n// - plan-strategy D-7 (AudioErrorCode SSOT in engine-types, parallel to\n// ImageErrorCode / GltfErrorCode / AssetErrorCode)\n// - plan-strategy section 8 AI User Affordance (structured 4-field surface:\n// .code / .expected / .hint / .detail)\n// - charter P3 (explicit failure: switch (err.code) exhaustive without default;\n// .hint provides concrete recovery action)\n// - charter P4 (consistent abstraction: structurally parallel to AssetError,\n// ImageError, GltfError same 4-field shape)\n// - architecture-principles #1 SSOT (the 5 literals + class shape + hints table\n// live here once; engine-audio package references this module)\n\n/**\n * Closed `AudioErrorCode` union -- 5 members (plan-strategy D-7;\n * requirements S-8). Exhaustive `switch (err.code)` needs no default\n * fallback -- TypeScript guards union completeness at compile time\n * (charter P3 explicit failure).\n *\n * Domain-separated from `AssetErrorCode` (runtime registry surface, 12 members)\n * and `GltfErrorCode` (importer surface, 13 members). AI users face these 5\n * alternatives at the audio engine surface (`@forgeax/engine-audio`\n * AudioError + `@forgeax/engine-audio-webaudio` backend).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'context-creation-failed'` | `new AudioContext()` threw or returned null (privacy browser / no audio device) |\n * | `'decode-failed'` | `decodeAudioData(arrayBuffer)` rejected (corrupt file / unsupported codec) |\n * | `'context-suspended'` | `play()` called while AudioContext.state is `'suspended'` and gesture listener failed to resume |\n * | `'invalid-clip-handle'` | AudioSource.clip handle is dangling or refers to an unregistered asset |\n * | `'bus-not-found'` | AudioSource.bus refers to a string literal outside the `'sfx' | 'music'` closed set |\n */\nexport type AudioErrorCode =\n | 'context-creation-failed'\n | 'decode-failed'\n | 'context-suspended'\n | 'invalid-clip-handle'\n | 'bus-not-found';\n\n/**\n * Per-code `AudioError` detail shapes -- discriminated payloads narrowed\n * by `AudioError.code` so AI users writing `switch (err.code)` get\n * control-flow-tightened access to the relevant detail fields\n * (charter P3 explicit failure).\n */\n\n/** `context-creation-failed` payload: carries the original error reason. */\nexport interface AudioCtxCreationFailedDetail {\n readonly code: 'context-creation-failed';\n readonly reason: string;\n}\n\n/** `decode-failed` payload: carries the original decode error reason. */\nexport interface AudioDecodeFailedDetail {\n readonly code: 'decode-failed';\n readonly reason: string;\n}\n\n/** `context-suspended` payload: empty marker detail (no extra fields). */\nexport interface AudioCtxSuspendedDetail {\n readonly code: 'context-suspended';\n}\n\n/** `invalid-clip-handle` payload: carries the dangling handle identifier. */\nexport interface AudioInvalidClipHandleDetail {\n readonly code: 'invalid-clip-handle';\n readonly clipHandleId: number;\n}\n\n/** `bus-not-found` payload: carries the invalid bus name attempted. */\nexport interface AudioBusNotFoundDetail {\n readonly code: 'bus-not-found';\n readonly attemptedBus: string;\n}\n\n/**\n * Discriminated detail union for `AudioError`, narrowed per `AudioError.code`.\n * AI users obtain the concrete detail shape via `switch (err.code)` without\n * needing a fallback `as` cast (charter P3).\n */\nexport type AudioErrorDetail =\n | AudioCtxCreationFailedDetail\n | AudioDecodeFailedDetail\n | AudioCtxSuspendedDetail\n | AudioInvalidClipHandleDetail\n | AudioBusNotFoundDetail;\n\n/**\n * Structured audio error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `@forgeax/engine-types`\n * `AssetError` + `ImageError` + `GltfError` same-shape errors\n * (charter P4 consistent abstraction; AGENTS.md \"Errors are structured.\n * Return Result, never throw for expected failures.\").\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'decode-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter P3 explicit failure red line).\n *\n * The `.message` field is auto-composed for human stack traces and carries\n * the same content as `.code` + `.expected` + `.hint`; AI users prefer\n * field access on the structured triple.\n *\n * @example AI-user exhaustive switch on the 5 members (no default fallback)\n * ```ts\n * import { AudioError, type AudioErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: AudioErrorCode): string {\n * switch (code) {\n * case 'context-creation-failed': return 'check browser supports AudioContext';\n * case 'decode-failed': return 'ensure audio file is a valid wav/mp3/ogg/flac';\n * case 'context-suspended': return 'call play after user gesture to trigger resume';\n * case 'invalid-clip-handle': return 'verify clip was registered via AssetRegistry';\n * case 'bus-not-found': return 'use sfx or music bus literal';\n * }\n * }\n * ```\n */\nexport class AudioError extends Error {\n readonly code: AudioErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: AudioErrorDetail;\n\n constructor(args: {\n code: AudioErrorCode;\n expected: string;\n hint: string;\n detail?: AudioErrorDetail;\n }) {\n super(`[AudioError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'AudioError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy D-7 lock-in).\n * Exported so engine-audio error helpers and tests consume the same SSOT\n * -- any drift here updates both producer call sites and the AGENTS.md\n * Error model table.\n *\n * The shape is a `Record<AudioErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter P3 explicit failure). Each hint embeds an executable recovery\n * action so AI users self-repair (charter P3).\n */\nexport const AUDIO_ERROR_HINTS: Readonly<Record<AudioErrorCode, string>> = {\n 'context-creation-failed':\n 'check browser supports AudioContext; verify no privacy extension blocks audio; try reloading the page after user gesture',\n 'decode-failed':\n 'ensure audio file is a valid wav/mp3/ogg/flac at the GUID path; check file integrity (truncated or empty bytes)',\n 'context-suspended':\n 'call play after a user gesture (click/tap/keydown) to trigger AudioContext.resume(); if in iframe check sandbox attribute',\n 'invalid-clip-handle':\n 'verify clip was registered via AssetRegistry.register() before spawning AudioSource; inspect active handles via assetRegistry.inspect()',\n 'bus-not-found':\n \"use 'sfx' or 'music' bus literal; custom bus names are not supported in v1 (OOS-2)\",\n};\n\n// === PhysicsErrorCode / PhysicsError / PhysicsErrorDetail -- physics error SSOT (feat-20260528-rapier-physics-2d-3d M1 / t6; extended feat-20260617-kinematic M1) ===\n//\n// Decision anchors:\n// - requirements AC-11 (PhysicsErrorCode closed union registration + AGENTS.md update)\n// - plan-strategy D-5 (PhysicsErrorCode 9 members / PhysicsError 4-field surface / PhysicsErrorDetail discriminated)\n// - charter P3 (explicit failure: exhaustive switch without default; .hint provides recovery)\n// - charter P4 (consistent abstraction: structurally parallel to AssetError / AudioError / GltfError)\n// - architecture-principles #1 SSOT (the 9 literals + class + hints table live here once;\n// engine-physics package re-exports from here)\n\n/**\n * Closed `PhysicsErrorCode` union -- 9 members (plan-strategy D-5;\n * requirements AC-11). Exhaustive `switch (err.code)` needs no default\n * fallback -- TypeScript guards union completeness at compile time\n * (charter P3 explicit failure).\n *\n * Domain-separated from `AssetErrorCode` (runtime registry, 13 members)\n * and `AudioErrorCode` (audio engine, 5 members). AI users face these 9\n * alternatives at the physics engine surface.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'wasm-load-failed'` | dynamic import() of Rapier WASM rejected (network / file not found). |\n * | `'wasm-simd-unsupported'` | WebAssembly.validate returned false for SIMD test module; compat fallback also unavailable. |\n * | `'step-failed'` | Rapier World.step threw a WASM trap (invalid body parameters / NaN values). |\n * | `'invalid-body-config'` | mass <= 0 for dynamic bodies, or other validation failure. |\n * | `'body-not-found'` | entity handle resolved to no Rapier rigid body (no RigidBody spawned or handle was freed). |\n * | `'collider-not-found'` | entity handle resolved to no Rapier collider (no Collider spawned or handle was freed). |\n * | `'backend-not-registered'` | PhysicsWorld resource missing from World; use createApp(canvas, { plugins: [physicsPlugin('rapier-3d')] }) or manual registration. |\n * | `'teleport-invalid-body-type'` | teleport() called on a static or kinematic body (only dynamic allowed). |\n * | `'controller-requires-kinematic'` | moveAndSlide() called on a non-kinematic body. |\n */\nexport type PhysicsErrorCode =\n | 'wasm-load-failed'\n | 'wasm-simd-unsupported'\n | 'step-failed'\n | 'invalid-body-config'\n | 'body-not-found'\n | 'collider-not-found'\n | 'backend-not-registered'\n | 'teleport-invalid-body-type'\n | 'controller-requires-kinematic';\n\n/**\n * Per-code `PhysicsError` detail shapes -- discriminated payloads narrowed\n * by `PhysicsError.code` so AI users writing `switch (err.code)` get\n * control-flow-tightened access to the relevant detail fields (charter P3).\n */\n\n/** `wasm-load-failed` payload: carries the original error reason. */\nexport interface PhysicsWasmLoadFailedDetail {\n readonly code: 'wasm-load-failed';\n readonly reason: string;\n}\n\n/** `wasm-simd-unsupported` payload: carries the detection failure reason. */\nexport interface PhysicsWasmSimdUnsupportedDetail {\n readonly code: 'wasm-simd-unsupported';\n readonly reason: string;\n}\n\n/** `step-failed` payload: carries the WASM trap reason. */\nexport interface PhysicsStepFailedDetail {\n readonly code: 'step-failed';\n readonly reason: string;\n}\n\n/** `invalid-body-config` payload: carries the violating field + value. */\nexport interface PhysicsInvalidBodyConfigDetail {\n readonly code: 'invalid-body-config';\n readonly field: string;\n readonly value: unknown;\n}\n\n/** `body-not-found` payload: carries the entity that was not found. */\nexport interface PhysicsBodyNotFoundDetail {\n readonly code: 'body-not-found';\n readonly entity: number;\n}\n\n/** `collider-not-found` payload: carries the entity that was not found. */\nexport interface PhysicsColliderNotFoundDetail {\n readonly code: 'collider-not-found';\n readonly entity: number;\n}\n\n/** `backend-not-registered` payload: carries the attempted backend name. */\nexport interface PhysicsBackendNotRegisteredDetail {\n readonly code: 'backend-not-registered';\n readonly attemptedBackend: string;\n}\n\n/** `teleport-invalid-body-type` payload: carries the entity + disallowed body type. */\nexport interface PhysicsTeleportInvalidBodyTypeDetail {\n readonly code: 'teleport-invalid-body-type';\n readonly entity: number;\n readonly bodyType: string;\n}\n\n/** `controller-requires-kinematic` payload: carries the entity + actual body type. */\nexport interface PhysicsControllerRequiresKinematicDetail {\n readonly code: 'controller-requires-kinematic';\n readonly entity: number;\n readonly bodyType: string;\n}\n\n/**\n * Discriminated detail union for `PhysicsError`, narrowed per `PhysicsError.code`.\n * AI users obtain the concrete detail shape via `switch (err.code)` without\n * needing a fallback `as` cast (charter P3).\n */\nexport type PhysicsErrorDetail =\n | PhysicsWasmLoadFailedDetail\n | PhysicsWasmSimdUnsupportedDetail\n | PhysicsStepFailedDetail\n | PhysicsInvalidBodyConfigDetail\n | PhysicsBodyNotFoundDetail\n | PhysicsColliderNotFoundDetail\n | PhysicsBackendNotRegisteredDetail\n | PhysicsTeleportInvalidBodyTypeDetail\n | PhysicsControllerRequiresKinematicDetail;\n\n/**\n * Structured physics error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `@forgeax/engine-types`\n * `AssetError` + `AudioError` + `GltfError` (charter P4 consistent abstraction).\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'wasm-load-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter P3 explicit failure red line).\n *\n * @example AI-user exhaustive switch on the 9 members (no default fallback)\n * ```ts\n * import { PhysicsError, type PhysicsErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: PhysicsErrorCode): string {\n * switch (code) {\n * case 'wasm-load-failed': return 'check network and @dimforge/rapier3d-compat';\n * case 'wasm-simd-unsupported': return 'check browser supports WASM SIMD';\n * case 'step-failed': return 'check for NaN values in transforms';\n * case 'invalid-body-config': return 'ensure mass > 0 for dynamic bodies';\n * case 'body-not-found': return 'ensure RigidBody was spawned before use';\n * case 'collider-not-found': return 'ensure Collider was spawned before use';\n * case 'backend-not-registered': return 'use createApp(canvas, { plugins: [physicsPlugin(...)] })';\n * case 'teleport-invalid-body-type': return 'only dynamic bodies can be teleported';\n * case 'controller-requires-kinematic': return 'set RigidBody.type to kinematic';\n * }\n * }\n * ```\n */\nexport class PhysicsError extends Error {\n readonly code: PhysicsErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: PhysicsErrorDetail;\n\n constructor(args: {\n code: PhysicsErrorCode;\n expected: string;\n hint: string;\n detail?: PhysicsErrorDetail;\n }) {\n super(`[PhysicsError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'PhysicsError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy D-5 lock-in).\n * Exported so engine-physics error helpers and tests consume the same SSOT.\n *\n * The shape is a `Record<PhysicsErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter P3 explicit failure).\n */\nexport const PHYSICS_ERROR_HINTS: Readonly<Record<PhysicsErrorCode, string>> = {\n 'wasm-load-failed':\n 'dynamic import() of Rapier WASM rejected; check network, file path, and that @dimforge/rapier3d-compat is installed',\n 'wasm-simd-unsupported':\n 'WebAssembly.validate returned false for the SIMD test module; ensure browser supports WASM SIMD (Chrome 91+, Firefox 89+, Safari 16.4+)',\n 'step-failed':\n 'Rapier World.step threw a WASM trap; check for invalid body parameters or NaN values in transforms',\n 'invalid-body-config':\n 'check mass > 0 for dynamic bodies and valid shape parameters; see PhysicsError.detail.field',\n 'body-not-found':\n 'the entity handle did not resolve to a Rapier rigid body; ensure RigidBody was spawned before calling physics APIs',\n 'collider-not-found':\n 'the entity handle did not resolve to a Rapier collider; ensure Collider was spawned before calling physics APIs',\n 'backend-not-registered':\n \"PhysicsWorld resource not found; use createApp(canvas, { plugins: [physicsPlugin('rapier-3d')] }) or manually register a backend\",\n 'teleport-invalid-body-type':\n 'teleport is only valid for dynamic bodies; static and kinematic bodies have their position managed differently',\n 'controller-requires-kinematic':\n \"moveAndSlide requires a kinematic RigidBody; set the entity's RigidBody.type to 'kinematic'\",\n};\n\n// === RuntimeErrorCode - runtime-layer error code SSOT (feat-20260523-skin-skeleton-animation M0) ===\n//\n// Closed union of runtime-layer error code literals. Defined here as the\n// single source of truth for code-string discovery (charter F1: AI users\n// grep '@forgeax/engine-types' for all error code families). The error\n// classes that carry these codes live in @forgeax/engine-runtime.\n//\n// Decision anchors:\n// - requirements AC-29 (RuntimeErrorCode +6: skin-joint-count-exceeded /\n// skin-joint-despawned / skin-joint-path-unresolved /\n// skin-instances-coexist-forbidden / vertex-storage-buffer-unavailable /\n// skin-palette-overflow)\n// - plan-strategy D-12 (kebab-case + closed union)\n// - charter P3 (explicit failure: exhaustive switch without default)\n\n/** Closed union of runtime-layer error codes. */\nexport type RuntimeErrorCode =\n | 'shadow-invalid-config'\n | 'skin-joint-count-exceeded'\n | 'skin-joint-despawned'\n | 'skin-joint-path-unresolved'\n | 'skin-instances-coexist-forbidden'\n | 'vertex-storage-buffer-unavailable'\n | 'skin-palette-overflow'\n | 'material-resolved-empty-passes'\n | 'equirect-projection-failed'\n | 'mesh-ssbo-capacity-exceeded'\n | 'mesh-ssbo-ceiling-reached'\n | 'hdrp-caps-insufficient'\n | 'hdrp-light-budget-exceeded'\n | 'hdrp-index-list-overflow';\n\n// === GPUFlagsConstant namespace numeric aliases (5 *Flags + 8 Size/Index/Offset/SampleMask) ===\n//\n// One-to-one with the W3C CR §3.6 `unsigned long` definitions; runtime values are\n// surfaced by the global objects (GPUBufferUsage / GPUColorWrite / GPUMapMode /\n// GPUShaderStage / GPUTextureUsage).\n\n/** GPU buffer usage bit flags (OR combination of GPUBufferUsage.MAP_READ / COPY_SRC / ...). */\nexport type BufferUsageFlags = GPUBufferUsageFlags;\n\n/** GPU color write mask bit flags (GPUColorWrite.RED / GREEN / BLUE / ALPHA / ALL). */\nexport type ColorWriteFlags = GPUColorWriteFlags;\n\n/** GPU buffer map mode bit flags (GPUMapMode.READ / WRITE). */\nexport type MapModeFlags = GPUMapModeFlags;\n\n/** GPU shader stage bit flags (GPUShaderStage.VERTEX / FRAGMENT / COMPUTE). */\nexport type ShaderStageFlags = GPUShaderStageFlags;\n\n/** GPU texture usage bit flags (GPUTextureUsage.COPY_SRC / COPY_DST / TEXTURE_BINDING / ...). */\nexport type TextureUsageFlags = GPUTextureUsageFlags;\n\n/** GPU 32-bit unsigned size. */\nexport type Size32 = GPUSize32;\n\n/** GPU 64-bit unsigned size. */\nexport type Size64 = GPUSize64;\n\n/** GPU 32-bit unsigned index. */\nexport type Index32 = GPUIndex32;\n\n/** GPU 32-bit signed offset. */\nexport type SignedOffset32 = GPUSignedOffset32;\n\n/** GPU integer coordinate (used for texture / viewport extents). */\nexport type IntegerCoordinate = GPUIntegerCoordinate;\n\n/** GPU sample mask bit pattern. */\nexport type SampleMask = GPUSampleMask;\n\n/** GPU buffer dynamic offset. */\nexport type BufferDynamicOffset = GPUBufferDynamicOffset;\n\n/** GPU stencil value (reference / read mask / write mask). */\nexport type StencilValue = GPUStencilValue;\n\n// === String literal enum re-exports (already exported by @webgpu/types; we only alias) ===\n\n/** GPU texture format enum (e.g. 'rgba8unorm' / 'depth24plus' / ...). */\nexport type TextureFormat = GPUTextureFormat;\n\n/** GPU texture dimension ('1d' / '2d' / '3d'). */\nexport type TextureDimension = GPUTextureDimension;\n\n/** GPU texture view dimension ('1d' / '2d' / '2d-array' / 'cube' / 'cube-array' / '3d'). */\nexport type TextureViewDimension = GPUTextureViewDimension;\n\n/** GPU compare function ('never' / 'less' / 'equal' / 'less-equal' / 'greater' / 'not-equal' / 'greater-equal' / 'always'). */\nexport type CompareFunction = GPUCompareFunction;\n\n/** GPU filter mode ('nearest' / 'linear'). */\nexport type FilterMode = GPUFilterMode;\n\n/** GPU address mode ('clamp-to-edge' / 'repeat' / 'mirror-repeat'). */\nexport type AddressMode = GPUAddressMode;\n\n/** GPU vertex format enum ('float32' / 'float32x2' / ... 32 variants in total). */\nexport type VertexFormat = GPUVertexFormat;\n\n/** GPU vertex step mode ('vertex' / 'instance'). */\nexport type VertexStepMode = GPUVertexStepMode;\n\n/** GPU index format ('uint16' / 'uint32'). */\nexport type IndexFormat = GPUIndexFormat;\n\n/** GPU primitive topology ('point-list' / 'line-list' / 'line-strip' / 'triangle-list' / 'triangle-strip'). */\nexport type PrimitiveTopology = GPUPrimitiveTopology;\n\n/** GPU triangle cull mode ('none' / 'front' / 'back'). */\nexport type CullMode = GPUCullMode;\n\n/** GPU triangle front-face winding ('ccw' / 'cw'). */\nexport type FrontFace = GPUFrontFace;\n\n/** GPU stencil operation ('keep' / 'zero' / 'replace' / 'invert' / 'increment-clamp' / 'decrement-clamp' / 'increment-wrap' / 'decrement-wrap'). */\nexport type StencilOperation = GPUStencilOperation;\n\n/** GPU blend factor ('zero' / 'one' / 'src' / 'one-minus-src' / ...). */\nexport type BlendFactor = GPUBlendFactor;\n\n/** GPU blend operation ('add' / 'subtract' / 'reverse-subtract' / 'min' / 'max'). */\nexport type BlendOperation = GPUBlendOperation;\n\n/** GPU load op ('load' / 'clear'). */\nexport type LoadOp = GPULoadOp;\n\n/** GPU store op ('store' / 'discard'). */\nexport type StoreOp = GPUStoreOp;\n\n// === Shader pipeline trio SSOT (feat-20260508-shader-pipeline-mvp) =================\n//\n// Decision anchors:\n// - plan-strategy §S-7 + §S-9 (fully-explicit reflection + ShaderError 5-field top level)\n// - requirements §AC-04 (manifest 4 fields) + MVP-2.6 (manifest schema TS SSOT)\n// - research Finding 2 (reflection JSON field-mapping oracle, 9 boundary cases)\n// - charter proposition 4 (explicit failure) + proposition 5 (consistent abstraction:\n// dev-time and runtime errors share one shape)\n\n/**\n * Single shader manifest entry — trio + 4-field manifest SSOT (AC-04).\n *\n * | Field | Shape | Notes |\n * |:--|:--|:--|\n * | `hash` | `string` | content-addressable fingerprint (the on-disk key written by the plugin's `generateBundle`) |\n * | `wgsl` | `string` | WGSL source: relative path or inline literal (the plugin chooses; schema does not constrain) |\n * | `glsl` | `string \\| undefined` | GLSL placeholder (empty string or undefined within M1 scope; reserved for the non-WebGL fallback path) |\n * | `bindings` | `string` | `BindGroupLayoutDescriptor[]` serialized as a JSON string (output derived from reflection) |\n *\n * Written by `@forgeax/engine-shader-compiler`, persisted by `@forgeax/engine-vite-plugin-shader`,\n * loaded and consumed by `@forgeax/engine-shader` — the schema's single source of truth lives\n * in this package across all three sides (charter proposition 5: consistent abstraction).\n */\nexport interface ManifestEntry {\n readonly hash: string;\n readonly wgsl: string;\n readonly glsl: string | undefined;\n readonly bindings: string;\n}\n\n/**\n * Shader compile-time error-code closed union — 7 members\n * (feat-20260512-naga-oil-composition-hmr M3 T-09 extension; D-R7 / S-7 /\n * OQ-2 legacy 4 + D-08 new 3 for naga_oil composition).\n *\n * Symmetric in shape with `@forgeax/engine-rhi`'s `RhiErrorCode` closed union\n * (AGENTS.md error model); exhaustive `switch` needs no default fallback —\n * TypeScript guards union completeness at compile time (charter proposition 4\n * explicit failure / proposition 3 machine-readable union > prose).\n *\n * Evolution: minor-add (requirements §AC-08). The 4 legacy positions remain\n * byte-for-byte at the top (AGENTS.md `Evolution contract`: members can be\n * added only — no rename / delete / reorder). The 3 new members appear at the\n * bottom:\n * - `shader-import-not-found` — naga_oil `ImportNotFound` variant surfaces\n * when `#import <moduleId>::<symbol>` cannot bind to any module registered\n * through `options.imports` (plan-strategy D-08 + D-12 offset passthrough).\n * - `shader-circular-import` — TS-layer DFS (T-11 `detectCycle`) catches\n * `a -> b -> a` style import cycles before calling into the wasm composer\n * (plan-strategy D-03 path A + D-04 cycle first/last repetition form).\n * - `shader-define-conflict` — TS-layer pre-scan (T-12 `scanDefineConflicts`)\n * rejects the same `#define NAME` appearing in >=2 modules (plan-strategy\n * D-07; prevents naga_oil HashMap silent override from research R-07).\n *\n * | code | Trigger |\n * |:--|:--|\n * | `'shader-compile-failed'` | naga `parse_str` / `Validator` failed; also the fallback for any non-ImportNotFound naga_oil ComposerError variant (plan-strategy D-05 non-boolean #define value goes here, never a new 8th member). |\n * | `'compiler-init-failed'` | wasm load / `init()` failed (cold start / missing wasm artifact). |\n * | `'manifest-malformed'` | manifest.json schema validation failed (4 fields missing or JSON unparseable). |\n * | `'shader-not-found'` | `ShaderRegistry.get(hash)` hash miss. |\n * | `'shader-import-not-found'` | `#import <moduleId>` target absent from `options.imports` (or lacks `#define_import_path` header). `err.detail.importPath` + `err.detail.fromModuleId` narrow after the switch. |\n * | `'shader-circular-import'` | import dependency graph contains a cycle; `err.detail.cycle` carries the full chain with first/last repeated (D-04). |\n * | `'shader-define-conflict'` | same `#define NAME` declared in multiple modules; `err.detail.sites[]` lists each offending moduleId. |\n */\nexport type ShaderErrorCode =\n | 'shader-compile-failed'\n | 'compiler-init-failed'\n | 'manifest-malformed'\n | 'shader-not-found'\n | 'shader-import-not-found'\n | 'shader-circular-import'\n | 'shader-define-conflict'\n // === 5 new material-* codes (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'material-schema-mismatch'\n | 'material-shader-not-found'\n | 'material-param-type-mismatch'\n | 'material-param-unknown'\n | 'material-param-missing-required'\n // === build-time superset gate (feat-20260613-material-paramschema-driven-binding M2 / w9) ===\n | 'material-shader-binding-mismatch';\n\n// === Shader error detail discriminated union (feat-20260512 M3 T-09 / D-08) =====\n//\n// Decision anchors:\n// - plan-strategy §2 D-08 (3 new typed variants keyed on `code`, structurally\n// parallel to `RhiErrorDetail` in `packages/rhi/src/errors.ts` lines\n// 165-189; 4 legacy members stay as prose detail for backwards compat).\n// - plan-strategy §2 D-04 (cycle first/last repetition form: ['a','b','a']).\n// - plan-strategy §2 D-12 (ImportNotFound offset passthrough when naga_oil\n// carries a source position on the inner variant).\n// - requirements §AC-08 (AGENTS.md §Error model ShaderErrorDetail row 3\n// variants) + §AC-15 (property access over string parsing).\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — narrow via `switch (err.detail.code)` after the\n// `switch (err.code)` tier).\n// - architecture-principles #1 SSOT (3 typed variants live here once;\n// producer site `packages/shader-compiler/src/error-mapper.ts` constructs\n// them verbatim; AGENTS.md §Error model table references this module).\n\n/**\n * Detail for the `shader-import-not-found` path (D-08 + D-12).\n *\n * `importPath` mirrors the bare `#import` target string (`'forgeax_pbr::brdf'`).\n * `fromModuleId` identifies the entry module that issued the unresolved\n * import; when the caller omitted `options.id`, this carries the\n * `<anonymous-entry-<hash8>>` placeholder (plan-strategy D-11). Optional\n * `offset` passes through the naga_oil inner-variant byte offset when present\n * (D-12); AI users surface this in error logs for IDE jump-to-source.\n */\nexport interface ShaderImportNotFoundDetail {\n readonly code: 'shader-import-not-found';\n readonly importPath: string;\n readonly fromModuleId: string;\n readonly offset?: number;\n}\n\n/**\n * Detail for the `shader-circular-import` path (D-08 + D-04).\n *\n * `cycle` lists the full import chain with the first and last element\n * repeated so consumers can visualise the loop at a glance\n * (`['a','b','c','a']`). The array is `readonly` so copy-out sites cannot\n * mutate the structure post-emit (charter proposition 4 explicit failure).\n */\nexport interface ShaderCircularImportDetail {\n readonly code: 'shader-circular-import';\n readonly cycle: readonly string[];\n}\n\n/**\n * Detail for the `shader-define-conflict` path (D-08 + D-07).\n *\n * `defineName` names the offending `#define NAME` literal; `sites` lists each\n * moduleId that declared it so the AI user can navigate to every duplicate\n * without re-scanning the source set (charter proposition 3 machine-readable\n * > prose).\n */\nexport interface ShaderDefineConflictDetail {\n readonly code: 'shader-define-conflict';\n readonly defineName: string;\n readonly sites: readonly { readonly moduleId: string }[];\n}\n\n/**\n * Detail for the `shader-compile-failed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * `compilerMessages` forwards the full 6 fields of `GPUCompilationMessage`\n * from `@webgpu/types ^0.1.69` (`message` / `type` / `lineNum` / `linePos` /\n * `offset` / `length`); the array is `readonly` so copy-out sites cannot\n * mutate the structure post-emit (charter proposition 4 explicit failure).\n * Optional `reason` carries a prose supplement when the wasm side surfaces a\n * higher-level summary alongside the raw compiler frame.\n *\n * @see RhiShaderCompileDetail in @forgeax/engine-rhi for the RhiError parallel\n * (R-7 namespace separation: `ShaderError.detail` vs `RhiError.detail` cover\n * disjoint lifecycle phases — compile-time vs async runtime dispatch — and\n * AI users distinguish them by import path).\n */\nexport interface ShaderCompileFailedDetail {\n readonly code: 'shader-compile-failed';\n readonly compilerMessages: readonly GPUCompilationMessage[];\n readonly reason?: string;\n}\n\n/**\n * Detail for the `compiler-init-failed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * Constructed by `@forgeax/engine-naga` when the wasm cold start fails\n * (`ensureReady()` rejects, the artefact is missing, or `init()` itself\n * throws). The `code` literal narrows `.detail` after the top-level\n * `switch (err.code)`; optional `reason` carries the wasm-side error message\n * when available (charter proposition 3 machine-readable union > prose).\n */\nexport interface ShaderInitFailedDetail {\n readonly code: 'compiler-init-failed';\n readonly reason?: string;\n}\n\n/**\n * Detail for the `manifest-malformed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * Constructed by `@forgeax/engine-naga` / `@forgeax/engine-shader-compiler`\n * when the shader manifest fails the 4-field schema (`{hash, wgsl, glsl,\n * bindings}`) or the JSON itself is unparseable. Optional `reason` carries\n * the schema validator or `JSON.parse` error message when available\n * (charter proposition 4 explicit failure: typed `.reason` access never\n * requires parsing `.message`).\n */\nexport interface ShaderManifestMalformedDetail {\n readonly code: 'manifest-malformed';\n readonly reason?: string;\n}\n\n// === 5 new material-* ShaderErrorDetail variants (feat-20260523-shader-template-instance-split M1-T02) ===\n//\n// Decision anchors:\n// - plan-strategy D-NewErrorCodes-Anchor (5 ShaderErrorCode + 5 detail variants in types SSOT)\n// - plan-strategy F-6 round 2 (material-schema-mismatch.mismatchKind is 4-element union:\n// schema-extra | shader-extra | type-mismatch | bg-overflow)\n// - requirements AC-12 (each new error code has structured detail)\n\n/**\n * Detail for `material-schema-mismatch` — paramSchema vs BGL mismatch at build-time.\n *\n * `mismatchKind` narrows on the 4-way mismatch category (F-6 round 2):\n * - 'schema-extra': paramSchema declares a name not in BGL\n * - 'shader-extra': BGL has a binding not in paramSchema\n * - 'type-mismatch': param type differs from BGL entry type\n * - 'bg-overflow': binding group count exceeds maxBindGroups (4) — AC-07\n *\n * Optional `expectedParam` / `actualBinding` carry the specific mismatch detail\n * for schema-extra / shader-extra / type-mismatch variants. `actualCount` /\n * `maxAllowed` populated for bg-overflow.\n */\nexport interface MaterialSchemaMismatchDetail {\n readonly code: 'material-schema-mismatch';\n readonly mismatchKind: 'schema-extra' | 'shader-extra' | 'type-mismatch' | 'bg-overflow';\n readonly materialShaderPath: string;\n readonly expectedParam?: string;\n readonly actualBinding?: number;\n readonly actualCount?: number;\n readonly maxAllowed?: number;\n}\n\n/**\n * Detail for `material-shader-not-found` — ShaderRegistry lookup miss.\n */\nexport interface MaterialShaderNotFoundDetail {\n readonly code: 'material-shader-not-found';\n readonly identifier: string;\n}\n\n/**\n * Detail for `material-param-type-mismatch` — a material value does not match\n * paramSchema expected type at runtime register.\n */\nexport interface MaterialParamTypeMismatchDetail {\n readonly code: 'material-param-type-mismatch';\n readonly paramName: string;\n readonly expectedType: string;\n readonly actualValue: unknown;\n}\n\n/**\n * Detail for `material-param-unknown` — material values contain a key not in\n * paramSchema.\n */\nexport interface MaterialParamUnknownDetail {\n readonly code: 'material-param-unknown';\n readonly paramName: string;\n}\n\n/**\n * Detail for `material-param-missing-required` — material values miss a key\n * that paramSchema declares without a default.\n */\nexport interface MaterialParamMissingRequiredDetail {\n readonly code: 'material-param-missing-required';\n readonly paramName: string;\n}\n\n/**\n * Detail for `material-shader-binding-mismatch` — vite-plugin-shader build-time\n * single-direction superset gate (feat-20260613-material-paramschema-driven-\n * binding M2 / D-9 / D-10).\n *\n * The actual reflected BGL must contain every binding emitted by\n * derive(schema); otherwise the build fails with this code. Extra bindings on\n * the actual side are tolerated (engine-injection placeholders such as shadow\n * / IBL / lightmap bind groups land at register-time).\n *\n * `expected` is the BGL entry derive(schema) emitted (the binding number +\n * resource layout the shader source must declare). `actual` is the entry the\n * reflector found at the same binding number, or `undefined` when the binding\n * is absent altogether. `expectedParam` names the paramSchema entry that\n * produced `expected` so AI users can grep the sidecar quickly. `mismatchKind`\n * narrows the failure category for AI-side branching.\n */\nexport interface MaterialShaderBindingMismatchDetail {\n readonly code: 'material-shader-binding-mismatch';\n readonly mismatchKind: 'binding-missing' | 'binding-type-mismatch';\n readonly materialShaderPath: string;\n readonly expected: BindGroupLayoutEntry;\n readonly actual?: BindGroupLayoutEntry;\n readonly expectedParam: string;\n}\n\n/**\n * Discriminated union of the 6 typed `.detail` variants keyed on `code`\n * (D-08 legacy 3 variants + feat-small-20260513-dx-docs-types-cleanup D-9\n * minor-add 3 variants; parallel to `RhiErrorDetail` lines 165-189 of\n * `packages/rhi/src/errors.ts`).\n *\n * AI users narrow to the per-code shape after the top-level\n * `switch (err.code)` via the nested `if (err.detail?.code === '<literal>')`\n * guard — `err.detail.compilerMessages` / `err.detail.importPath` /\n * `err.detail.reason` etc. are then typed property accesses with full IDE\n * autocomplete (charter proposition 3 machine-readable union > prose +\n * proposition 4 explicit failure).\n *\n * The 7th member `'shader-not-found'` has no typed detail variant — the naga\n * `shaderNotFound` factory leaves `.detail` undefined because the surface\n * carries no per-instance payload (the `hash` is already embedded in\n * `.message` / `.expected`; OOS-11 deferring a typed variant).\n *\n * Listed in the same order as the corresponding `ShaderErrorCode` members so\n * a reviewer can grep the two unions vertically for drift\n * (T-09 acceptance check ties `ShaderErrorDetail` grep hit to this layout).\n */\nexport type ShaderErrorDetail =\n | ShaderImportNotFoundDetail\n | ShaderCircularImportDetail\n | ShaderDefineConflictDetail\n | ShaderCompileFailedDetail\n | ShaderInitFailedDetail\n | ShaderManifestMalformedDetail\n // === 5 new material-* detail variants (feat-20260523-shader-template-instance-split M1-T02) ===\n | MaterialSchemaMismatchDetail\n | MaterialShaderNotFoundDetail\n | MaterialParamTypeMismatchDetail\n | MaterialParamUnknownDetail\n | MaterialParamMissingRequiredDetail\n // === build-time superset gate (feat-20260613-material-paramschema-driven-binding M2 / w9) ===\n | MaterialShaderBindingMismatchDetail;\n\n/**\n * Bind group layout descriptor — shape-aligned with\n * `Pick<GPUBindGroupLayoutDescriptor, 'entries' | 'label'>` (S-9 / AC-04).\n *\n * **Shape rules**:\n * - `entries` is narrowed here to a concrete `readonly BindGroupLayoutEntry[]` (the\n * spec uses `Iterable<...>`; reflection-derived output is always an array shape).\n * - All optional fields are uniformly `?: T | undefined` (guarded by\n * exactOptionalPropertyTypes).\n * - Field names match `@webgpu/types` exactly, character for character (spec-alignment rule).\n *\n * **Fully-explicit reflection JSON constraint** (plan-strategy §S-9 / D-R9):\n * the `bindings` JSON emitted by `@forgeax/engine-shader-compiler` must populate every default\n * field defined in W3C spec §5 (e.g. `hasDynamicOffset: false` / `minBindingSize: 0`);\n * `visibility` is output as the `GPUShaderStage` integer bitmask (VERTEX=0x1 /\n * FRAGMENT=0x2 / COMPUTE=0x4 OR-ed together) — string-array form is **forbidden**.\n * This type only describes the schema shape; full explicitness is enforced on the\n * producer side.\n */\nexport interface BindGroupLayoutDescriptor {\n readonly label?: string | undefined;\n readonly entries: readonly BindGroupLayoutEntry[];\n}\n\n/**\n * Single bind group layout entry — shape-aligned with\n * `@webgpu/types.GPUBindGroupLayoutEntry`.\n *\n * `binding` / `visibility` are required; the four resource layouts (buffer / sampler /\n * texture / storageTexture) form the \"exactly one set\" constraint per W3C spec §5\n * (`externalTexture` is out of scope for the forgeax MVP and is not surfaced here yet).\n */\nexport interface BindGroupLayoutEntry {\n readonly binding: GPUIndex32;\n readonly visibility: GPUShaderStageFlags;\n readonly buffer?: GPUBufferBindingLayout | undefined;\n readonly sampler?: GPUSamplerBindingLayout | undefined;\n readonly texture?: GPUTextureBindingLayout | undefined;\n readonly storageTexture?: GPUStorageTextureBindingLayout | undefined;\n}\n\n// === RemoteHandle (feat-20260629-inspector-two-layer-model M4 / w17) ========\n//\n// Decision anchors:\n// - plan-strategy secondary D-6: RemoteHandle defined in @forgeax/engine-types\n// (neutral package, no temporal coupling to @forgeax/engine-remote)\n// - requirements AC-11: app.remote typed as RemoteHandle | undefined,\n// exposed on the createApp return value for host inspection\n//\n// Shape:\n// port — number, the listen port (determined by the server on startup)\n// close — Promise<void>, tear down the server (Surface Plugin pattern\n// from startServer's returned ConsoleHandle)\n\n/**\n * Handle for a running remote eval server (feat-20260629-inspector-two-layer-model M4).\n *\n * AI users access `app.remote.port` for WS connection / status, and call\n * `await app.remote.close()` to tear down. The field is `undefined` when the\n * server is not started (production build or headless without opt-in).\n *\n * @see {@link startServer} in @forgeax/engine-remote for the producer side\n */\nexport interface RemoteHandle {\n /** Server listen port (number). Non-zero when the server is running. */\n readonly port: number;\n /** Tear down the server. Returns a Promise that resolves once the WS\n * server has closed all connections. */\n close(): Promise<void>;\n}\n\n// === Remote error model SSOT (feat-20260629-inspector-two-layer-model) ====\n//\n// Decision anchors:\n// - requirements §10.1 + §10.2 + AC-05 (`RemoteErrorCode` 5-member\n// closed union + structured `RemoteError` shape independent from RhiError /\n// ShaderError)\n// - plan-strategy §2 D-5 (rename InspectorErrorCode -> RemoteErrorCode,\n// delete inspector-write-denied, delete script-timeout, rename\n// console-* -> server-*)\n// - charter proposition 3 (machine-readable union > prose) +\n// proposition 4 (explicit failure — `switch (err.code)` is exhaustive\n// without default fallback) + proposition 5 (consistent abstraction —\n// structurally aligned with @forgeax/engine-rhi's RhiError surface)\n// - architecture-principles #1 SSOT (the 5 string literals + structured\n// structural shape live here once; @forgeax/engine-remote's runtime `RemoteError`\n// class implements this interface; consumers import the type\n// alias without dragging the runtime class through static deps —\n// parallel to the existing ShaderErrorCode pattern)\n\n/**\n * Closed `RemoteErrorCode` union — 5 members (feat-20260629-inspector-two-layer-model\n * D-5; requirements AC-05). Exhaustive `switch` needs no default\n * fallback — TypeScript guards union completeness at compile time\n * (charter proposition 4 explicit failure + proposition 3 machine-readable\n * union > prose).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'script-syntax-error'` | Script body is not parseable JavaScript (SyntaxError from eval). |\n * | `'script-runtime-error'` | Script threw a non-syntax exception during execution (e.g. ReferenceError / TypeError). |\n * | `'server-startup-failed'` | The remote eval server failed to come up: WebSocketServer raised 'error' (EADDRINUSE / other listen failure), dynamic-import resolution failed, or the target package lacks the `startServer` factory. |\n * | `'server-not-running'` | CLI client's `new WebSocket('ws://localhost:<port>/inspector')` failed to connect (server not started; `app.remote` not wired in the demo). |\n * | `'eval-result-not-serializable'` | A successful eval result cannot cross the JSON-RPC wire, such as a BigInt or cyclic object. |\n *\n * **Independence from `RhiError | ShaderError` union** — `RemoteErrorCode`\n * is **not** merged into the GPU / asset error union (charter proposition 5 +\n * architecture-principles #1 SSOT). Engine-side errors stream is OOS-1\n * (errors.subscribe v2 spinoff); remote callers only face these 5\n * alternatives.\n */\nexport type RemoteErrorCode =\n | 'script-syntax-error'\n | 'script-runtime-error'\n | 'server-startup-failed'\n | 'server-not-running'\n | 'eval-result-not-serializable';\n\n/**\n * Structural shape of a forgeax remote error (feat-20260629-inspector-two-layer-model\n * D-5). Structured surface mirroring `@forgeax/engine-rhi` `RhiError`\n * (charter proposition 5 consistent abstraction; AGENTS.md \"Errors are\n * structured\"):\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail).\n * - `.hint` actionable recovery guidance (L2 detail).\n * - `.message` auto-composed string for human stack traces (AI users\n * prefer property access on `.code` / `.expected` / `.hint`).\n * - `.name` Error name marker (`'RemoteError'`) for cross-realm\n * dispatch under JSON-RPC transport.\n *\n * This interface intentionally extends `Error` so a runtime `RemoteError`\n * **class** (defined in `@forgeax/engine-remote/errors`) satisfies the contract\n * without re-declaring the inherited `name` / `message` slots.\n *\n * AI users consume the structured triple via property access — never by\n * parsing `.message` (charter proposition 4 explicit failure red line).\n */\nexport interface RemoteError extends Error {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n /**\n * Optional discriminated detail payload (feat-20260517 D-7). Per-code\n * variant carries structured provenance that would otherwise pollute the\n * single-line `.hint` copy. AI users narrow via `switch (err.code)`; the\n * `.detail` slot is `undefined` for codes whose discriminator has no\n * payload (charter P4 explicit failure: signal absence by type).\n */\n readonly detail?: RemoteErrorDetail;\n}\n\n/**\n * Discriminated detail union for {@link RemoteError} (feat-20260517 D-7).\n * Each variant pairs a {@link RemoteErrorCode} member with the\n * structured payload AI users need to act on the error without grepping\n * prose. Variants without payload are intentionally absent — the\n * `RemoteError.detail` slot is `undefined` for those codes.\n *\n * The `server-startup-failed` variant carries bounded startup provenance.\n */\nexport type RemoteErrorDetail = ServerStartupFailedDetail | EvalResultNotSerializableDetail;\n\n/**\n * `server-startup-failed` discriminator variant with bounded provenance.\n */\nexport interface ServerStartupFailedDetail {\n readonly code: 'server-startup-failed';\n readonly removedAt: string;\n readonly docAnchor: string;\n}\n\n/**\n * `eval-result-not-serializable` discriminator variant. The shape is a\n * bounded classification only; it carries no part of the returned object\n * so failed transport cannot leak arbitrary engine state.\n */\nexport interface EvalResultNotSerializableDetail {\n readonly code: 'eval-result-not-serializable';\n readonly shape: 'bigint' | 'cyclic-object' | 'unsupported';\n}\n\n// === Metric registry error model SSOT (feat-20260512-threejs-pixel-parity-bench) ===\n//\n// Decision anchors:\n// - requirements §3.5 + AC-04 + AC-05 + AC-11 (`MetricErrorCode` 4-member closed\n// union elevated to TS alias; B-1 regression-prevention callout — exhaustive\n// `switch (err.code)` without `default:` must compile under tsc strict)\n// - plan-strategy §2 D-P3 (MetricErrorCode TS alias goes first in the topology;\n// M1 T-001 ships only the 4 legacy members verbatim from AGENTS.md Error\n// model table)\n// - research Finding 9 (`MetricErrorCode` currently has zero TS alias = direct\n// B-1 regression risk; §6 g9 checklist item 1: introduce\n// `export type MetricErrorCode = ...` in `packages/types/src/index.ts`,\n// structurally parallel to ShaderErrorCode / RemoteErrorCode)\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — closed-union exhaustive switch needs no default fallback;\n// tsc strict mode guards completeness) + proposition 5 (consistent abstraction —\n// structurally aligned with @forgeax/engine-rhi RhiError and RemoteError)\n// - architecture-principles #1 SSOT (the 4 string literals live here once;\n// `scripts/check-metrics-declared.mjs` / `scripts/metrics/run-all.mjs` /\n// `scripts/metrics/run-fps.mjs` are .mjs producer sites that emit the same\n// literals at throw points; parallel to ShaderErrorCode pattern)\n\n/**\n * Closed `MetricErrorCode` union — 4 members (M1 T-001 elevation of the\n * pre-existing 4 `.mjs` producer literals to a TS alias; research Finding 9\n * §6 g9 checklist item 1). Exhaustive `switch` needs no default fallback —\n * TypeScript guards union completeness at compile time (charter proposition 4\n * explicit failure + proposition 3 machine-readable union > prose).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'metric-not-declared'` | a workspace member lacks `package.json#forgeax.metrics` or the declaration is not a plain object; emitted by `scripts/check-metrics-declared.mjs` + `scripts/metrics/run-all.mjs`. |\n * | `'metric-kind-unknown'` | `forgeax.metrics` contains a key not in the closed `MetricKind` union (`bundle-size` / `fps` / `bench` / `gate` / `spike-report`); typo guard via ajv `additionalProperties: false`. |\n * | `'metric-status-not-ok'` | dispatcher (bundle-size / bench / gate / fps / spike-report) returned `status !== 'ok'`; the offending `report/<package>/<kind>.json` carries the value-vs-threshold detail. |\n * | `'metric-schema-malformed'` | `forgeax-metrics.schema.json` failed to parse / compile as JSON Schema 2020-12; precondition failure surfaced by both `check-metrics-declared.mjs` and `run-all.mjs`. |\n *\n * **B-1 regression prevention** (requirements AC-05 + AC-11): an alias without\n * a TS consumer site cannot be exhaustively switched; M1 T-002 adds type-level\n * tests against this alias, and M2 evaluator + M2 runner CLI add the two\n * non-test exhaustive `switch (err.code)` consumer sites (D-P9 plan-strategy\n * decision).\n *\n * Per-feat extension to 6 members (M1 T-002, D-P3): `'pixel-parity-threshold-exceeded'`\n * + `'pixel-parity-capture-failed'` extend the alias at the bottom; AGENTS.md\n * Error model table flips from `(4)` to `(6)` in lockstep. The two new members\n * encode the double-gate of the pixel-parity bench (research Finding 10 +\n * plan-strategy D-P2): Layer A per-pixel YIQ tolerance ` perPixelThreshold` is\n * pixelmatch-internal and never raises on its own; Layer B aggregate cap\n * `threshold` raises `'pixel-parity-threshold-exceeded'`; any capture-side\n * failure (chromium launch / vite preview / readPixels / size mismatch /\n * pixelmatch internal throw) collapses into `'pixel-parity-capture-failed'`\n * with a `.detail.stage` discriminator (charter proposition 5 consistent\n * abstraction — pixelmatch internal exception does NOT get a third member;\n * see D-P3 decision rationale).\n */\nexport type MetricErrorCode =\n | 'metric-not-declared'\n | 'metric-kind-unknown'\n | 'metric-status-not-ok'\n | 'metric-schema-malformed'\n | 'pixel-parity-threshold-exceeded'\n | 'pixel-parity-capture-failed';\n\n/**\n * Per-code detail shape for the four legacy `MetricErrorCode` members\n * (`'metric-not-declared'` / `'metric-kind-unknown'` / `'metric-status-not-ok'`\n * / `'metric-schema-malformed'`).\n *\n * The four legacy `.mjs` producer sites (`scripts/check-metrics-declared.mjs`,\n * `scripts/metrics/run-all.mjs`, `scripts/metrics/run-fps.mjs`) emit textual\n * `[reason] / [hint]` lines and never carry a structured payload — they live\n * in CI-only scripts and exit 1 directly. The `.detail` slot is therefore left\n * `undefined` so AI consumers do not waste a narrowing step looking for a\n * non-existent payload (charter proposition 4 explicit failure: signal absence\n * by type).\n */\nexport interface MetricLegacyDetail {\n readonly stage?: undefined;\n}\n\n/**\n * Detail shape exclusive to the `'pixel-parity-threshold-exceeded'` path\n * (M1 T-002 / D-P11). Carries the full numeric verdict so AI users can\n * surface the value-vs-threshold delta in stderr / sticky-comment renderings\n * without parsing `.message`.\n *\n * | Field | Meaning |\n * |:--|:--|\n * | `diffPixelCount` | Aggregate count from `pixelmatch(left, right, ...)` (Layer B reading). |\n * | `diffPercent` | `diffPixelCount / (width * height)` rendered as a 0..1 float for sticky-comment formatting. |\n * | `maxChannelDelta` | Maximum per-channel uint8 delta across all differing pixels (0..255). Helps disambiguate \"many tiny diffs\" from \"few big diffs\". |\n * | `threshold` | The declared Layer B integer cap (`package.json#forgeax.metrics.bench.pixelDiff.threshold`). |\n * | `perPixelThreshold` | The Layer A `pixelmatch` per-pixel YIQ float threshold actually used; equals the declared value or the `0.1` fallback (D-P2 default semantics). |\n *\n * The exhaustive discriminator is `code === 'pixel-parity-threshold-exceeded'`\n * — AI users access `.detail.diffPixelCount` directly after the type guard\n * with full IDE autocomplete (charter proposition 3 machine-readable union >\n * prose; AI-user review F-1 IDE autocomplete affordance).\n */\nexport interface ParityThresholdDetail {\n readonly diffPixelCount: number;\n readonly diffPercent: number;\n readonly maxChannelDelta: number;\n readonly threshold: number;\n readonly perPixelThreshold: number;\n}\n\n/**\n * Detail shape exclusive to the `'pixel-parity-capture-failed'` path (M1 T-002\n * / D-P11). Carries a discriminator `.stage` that pinpoints which step of the\n * capture pipeline collapsed (charter proposition 5 consistent abstraction:\n * pixelmatch-internal throw becomes `.stage='diff'` rather than a third\n * `MetricErrorCode` member — plan-strategy D-P3 decision).\n *\n * | `.stage` | trigger |\n * |:--|:--|\n * | `'chromium-launch'` | `chromium.launch({...})` threw (research Finding 6: `--enable-unsafe-webgpu` flag still rejected on the host). |\n * | `'vite-preview'` | spawned vite preview never reached `wait-on tcp 30s` (research Finding 4 cleanup pattern). |\n * | `'pixel-readback'` | `gl.readPixels(...)` or `commandEncoder.copyTextureToBuffer(...)` failed, or `window.__captureLeft/Right` was missing. |\n * | `'size-mismatch'` | left and right `Uint8Array.length` differ; `leftSize` / `rightSize` carry the actual byte counts. |\n * | `'diff'` | `pixelmatch(left, right, ...)` itself threw (charter proposition 4 explicit failure: no silent catch; EC-06). |\n *\n * Optional `leftSize` / `rightSize` are populated for the `'size-mismatch'`\n * stage; they are absent for the other stages because the failure happened\n * before any byte count was known.\n */\nexport interface ParityCaptureDetail {\n readonly stage: 'chromium-launch' | 'vite-preview' | 'pixel-readback' | 'size-mismatch' | 'diff';\n readonly leftSize?: number;\n readonly rightSize?: number;\n /**\n * Optional human-readable cause string for the failure (typically the\n * caught `Error.message` text or an inferred reason). Aligned with ECMA\n * 2022 `Error.cause` naming convention so IDE hover invokes the same mental\n * model. Filled by `scripts/bench/pixel-parity.mjs` at every stage that\n * surfaces a non-empty message; absent when the failure is purely\n * structural (e.g. `'size-mismatch'` where `leftSize` / `rightSize` carry\n * the diagnostic payload instead).\n */\n readonly cause?: string;\n}\n\n/**\n * Non-optional detail projection carried by `MetricError`.\n *\n * `MetricError` owns the complete code-to-detail relation. `NonNullable` removes\n * only the four legacy absence markers; parity payloads and the legacy detail\n * shape remain in the public family without a second manually maintained list.\n */\nexport type MetricErrorDetail = NonNullable<MetricError['detail']>;\n\n/**\n * Structural shape of a forgeax metric error (feat-20260512 T-002).\n *\n * Three-field surface (`.code` / `.expected` / `.hint`) plus per-code-narrowed\n * `.detail`, structurally aligned with `@forgeax/engine-rhi` `RhiError` and\n * `InspectorError` (charter proposition 5 consistent abstraction; AGENTS.md\n * \"Errors are structured. Return Result, never throw for expected failures\").\n *\n * `MetricError` is a TypeScript discriminated union of 6 per-code interfaces;\n * each variant narrows `.detail` to the corresponding `MetricErrorDetail`\n * branch. AI users perform a single `switch (err.code)` and pick up\n * `.detail.diffPixelCount` (threshold-exceeded path) or `.detail.stage`\n * (capture-failed path) with full IDE autocomplete (AI-user review F-1\n * affordance; D-P11).\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail; mirrors the `[reason]`\n * line emitted by `failStructured(...)` in the three `.mjs`\n * producer sites).\n * - `.hint` actionable recovery guidance (L2 detail; mirrors the `[hint]`\n * line in `failStructured(...)`).\n * - `.detail` path-specific structured payload narrowed per `.code`.\n *\n * AI users consume the structured triple via property access — never by\n * parsing `.message` (charter proposition 4 explicit failure red line).\n */\nexport type MetricError =\n | (MetricErrorBase & {\n readonly code: 'metric-not-declared';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-kind-unknown';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-status-not-ok';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-schema-malformed';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'pixel-parity-threshold-exceeded';\n readonly detail: ParityThresholdDetail;\n })\n | (MetricErrorBase & {\n readonly code: 'pixel-parity-capture-failed';\n readonly detail: ParityCaptureDetail;\n });\n\n/**\n * Common base of every `MetricError` variant (D-P11 internal helper —\n * never instantiated on its own, only intersected into the per-code\n * branches of `MetricError`).\n */\ninterface MetricErrorBase {\n readonly code: MetricErrorCode;\n readonly expected: string;\n readonly hint: string;\n}\n\n// === Pack-index catalog entry POD (feat-20260517-vite-plugin-image-build-time-cook D-2) ===\n//\n// PackIndexEntry is the in-memory shape of one row in `pack-index.json` (build\n// path) and `/__pack/index` JSON response (dev path). It is the SSOT contract\n// between the build-time catalog builder (`@forgeax/engine-vite-plugin-pack`)\n// and the runtime asset loader (`@forgeax/engine-runtime` `parseAssetPayload`).\n//\n// Decision anchors:\n// - plan-strategy D-2 (5-field metadata sub-structure: width / height /\n// format / colorSpace / mipmap, mirrors TextureAsset POD field names so\n// `metadata.colorSpace` greps to the same surface across catalog / POD /\n// sidecar).\n// - plan-strategy D-5 (sidecar `mipmap: 'auto' | 'none'` is mapped to the\n// `boolean` form by the catalog builder; runtime is unaware of the\n// string token).\n// - charter P1 (progressive disclosure -- core 4 fields stay flat,\n// image-only metadata sinks into a sub-structure that texture-arm\n// consumers narrow into).\n// - charter P4 (consistent abstraction -- `metadata` field-by-field\n// mirrors `TextureAsset` POD field names; `width` / `height` / `format`\n// / `colorSpace` / `mipmap` align byte-for-byte).\n//\n// Backward compatibility (D-2 'minor' evolution):\n// - `metadata` is `?: ImageMetadata | undefined` -- legacy 4-field entries\n// emitted by older builds (or future non-texture kinds: 'mesh' / 'scene' /\n// 'material') stay valid; runtime consumers narrow on `entry.metadata !==\n// undefined` before accessing fields.\n// - The interface stays open over `kind` (string) so future 'audio' /\n// 'video' arms can join without re-typing PackIndexEntry; the texture\n// arm narrows via `entry.kind === 'texture'` + `entry.metadata`\n// existence in `parseAssetPayload`.\n\n/**\n * Metadata sub-structure carried by `PackIndexEntry` rows of `kind: 'texture'`.\n *\n * Five fields mirror `TextureAsset` POD field names (`width` / `height` /\n * `format` / `colorSpace` / `mipmap`) so AI users can grep one identifier and\n * see the same surface in catalog rows, sidecar `*.meta.json`\n * `importSettings`, and the runtime `TextureAsset` POD (charter P4 consistent\n * abstraction).\n *\n * `width` / `height` are optional because dev-mode catalog rows folded from a\n * `*.meta.json` sidecar may lack pixel dimensions until `parseImage`\n * decodes the JPG bytes; build-mode (import) rows always have them filled\n * because the import step has already run `parseImage` to produce the RGBA\n * bytes.\n *\n * `format` is `GPUTextureFormat` to align with the `TextureAsset.format`\n * field (math-free, spec-aligned with `@webgpu/types ^0.1.70`).\n *\n * `colorSpace` and `mipmap` are required because the sidecar\n * `importSettings` always carries them (D-5: `'auto'` / `'none'` string\n * tokens are mapped to `true` / `false` at the catalog builder; runtime never\n * sees the string form).\n *\n * `compression` is the build-time compression level this image artefact\n * was stored with. Loop 1 supports `'none'` (passthrough) and `'zstd'`.\n * Loop 2 may add members like `'basis-uastc'`. Absent for legacy rows.\n */\n\n/**\n * Asset compression strategy — closed literal union (SSOT, D-3 / D-9).\n *\n * Five flat, mutually-exclusive members describing how an artefact is stored:\n * - `'none'` — pass-through (uncompressed bytes)\n * - `'zstd'` — generic zstd container compression (Loop 1)\n * - `'basis-etc1s'` / `'basis-uastc'` / `'basis-uastc-hdr'` — a Basis-encoded\n * KTX2 texture (Loop 2). The `basis-*` members fully describe the delivered\n * encoding: the KTX2 container carries its own supercompression (self-\n * described by the KTX2 header) and does NOT stack an outer `'zstd'` layer\n * (mutual exclusion by construction, D-3). The `basis-` kebab prefix is\n * visually distinct from GPU texture-format literals (naming rule, §8).\n *\n * Add-only-minor: Loop 2 appends the three `basis-*` members without repainting\n * `'none'` / `'zstd'` semantics (AC-11a). A missing / `undefined` field means a\n * legacy uncompressed artefact (E1 backward-compat).\n */\nexport type AssetCompression = 'none' | 'zstd' | 'basis-etc1s' | 'basis-uastc' | 'basis-uastc-hdr';\n\nexport interface ImageMetadata {\n readonly kind: 'texture';\n readonly width?: number;\n readonly height?: number;\n readonly format: GPUTextureFormat;\n readonly colorSpace: 'srgb' | 'linear';\n readonly mipmap: boolean;\n /** Build-time compression strategy used for this image artefact. `undefined` for legacy assets. */\n readonly compression?: AssetCompression;\n /**\n * Sidecar control-plane request for the offline texture encoder (D-12).\n * `'auto'` derives the delivery encoding from `colorSpace` + HDR source;\n * `'etc1s'` / `'uastc'` force a Basis encoding; `'none'` keeps the\n * uncompressed `.bin` path. Aligns with the mipmap sidecar tri-state idiom.\n * The `'auto'` default semantics activate in M5; M3 keeps the default `'none'`.\n */\n readonly compressionMode?: 'auto' | 'etc1s' | 'uastc' | 'none';\n /** Optional asset-owned cooked-payload target dimension. */\n readonly downscaleMaxDimension?: number;\n}\n\nexport type {\n AssetAuthoringCapability,\n AssetAuthoringUnavailableCode,\n AssetAuthoringUnavailableReason,\n AssetBindingCapability,\n AssetBindingTarget,\n AssetPlacementCapability,\n AssetRelation,\n AssetRelationPolicy,\n AssetRelationType,\n AssetSubjectRef,\n AssetSubjectType,\n CatalogDiagnostic,\n CatalogDiagnosticSeverity,\n CatalogLifecycle,\n CatalogOperationDescriptor,\n CatalogOperationName,\n CatalogOperations,\n CatalogProjection,\n CatalogProjectionInput,\n CatalogSubject,\n CookExecution,\n ExistingOutput,\n ImportedOutputDeclaration,\n KindChange,\n MatchConflict,\n ProducerContractDiagnostic,\n ProducerContractErrorCode,\n ProducerContractResult,\n ProposedOutput,\n ProviderProvenance,\n ResourceRevision,\n ScenePublicationFence,\n SourceOverrideDescriptor,\n SourceOverrideDiagnostic,\n SourceOverrideErrorCode,\n SourceOverrideMap,\n SourceOverridePayload,\n SourceOverrideValidationResult,\n TopologyConflictReason,\n TopologyDiff,\n TopologyPreserved,\n UiAuthoringCapability,\n UiAuthoringProjection,\n} from './asset-producer';\nexport {\n authoringCapabilityForAssetKind,\n canonicalizeSourceOverrides,\n catalogOperationsFor,\n isCatalogProjectionValid,\n MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA,\n validateSourceOverrideMap,\n} from './asset-producer';\n/**\n * One row in the pack-index catalog (`pack-index.json` for build path,\n * `/__pack/index` JSON response for dev path).\n *\n * Core fields (4) stay flat for AI users to grep one identifier:\n * - `guid`: UUIDv5/v7 lowercase string (asset identity SSOT)\n * - `packageUrl`: cooked Pack v2 package navigation URL.\n * - `kind`: closed-string discriminator (`'texture'` / `'mesh'` / `'scene'`\n * / `'material'` / future arms); narrowed by runtime `parseAssetPayload`\n * via exhaustive switch.\n * - `sourcePath`: relative path to the on-disk source artefact for\n * debugging + grep (dev: source JPG path; build: same source JPG path\n * even though `packageUrl` points to the cooked package).\n *\n * Optional 5th field:\n * - `metadata`: `ImageMetadata | undefined` -- present when `kind ===\n * 'texture'`; absent for non-texture kinds (legacy `.pack.json` entries\n * emit 4-field rows). Runtime consumers narrow with `entry.metadata !==\n * undefined` before consumption (D-2 backward-compat strategy).\n */\nexport type {\n AssetPublicationEnvelope,\n AssetPublicationEvidenceUsage,\n AssetPublicationExternalEvidence,\n AssetPublicationFailure,\n AssetPublicationFailureStage,\n AssetPublicationLocator,\n AssetPublicationOutput,\n AssetPublicationReceipt,\n AssetPublicationRecovery,\n CatalogDelta,\n CatalogEntry,\n CatalogEntry as PackIndexEntry,\n CatalogRevisionPoint,\n CatalogRevisionWindow,\n} from './catalog';\nexport type { CatalogEntryV2 } from './catalog.js';\n\n// === InspectEntry / InspectSnapshot (feat-20260618-asset-and-pack-name-fields M1 / w3) ===\n//\n// Decision anchors:\n// - plan-strategy D-9 (InspectEntry.name: string via resolveName, non-optional\n// with empty string as legal value; relocated from runtime private to types\n// for single-entry discoverability per charter F1)\n// - requirements AC-12 (inspector assets root carries resolved name per entry)\n//\n// These types were originally private interfaces in asset-registry.ts.\n// They are promoted to @forgeax/engine-types so console + future inspector\n// consumers import them from a single entry point (charter F1).\n\n/** One row in the inspector's `assets[]` snapshot (JSON-RPC over WS). */\nexport interface InspectEntry {\n readonly guid: string;\n /** Asset kind discriminant string (e.g. `'mesh'`, `'texture'`, `'scene'`). */\n readonly kind: string;\n /** Display name resolved by resolveName (empty string is legal). */\n readonly name: string;\n}\n\n/** Snapshot returned by `AssetRegistry.inspect()` -- the inspector root. */\nexport interface InspectSnapshot {\n readonly assets: ReadonlyArray<InspectEntry>;\n}\n\n// === Loader contract SSOT (feat-20260603-asset-import-loader-injection M1 / w3) ===\n//\n// Decision anchors:\n// - plan-strategy D-1 (runtime LoaderRegistry dispatches on `asset.kind`;\n// host injects loaders via `wireDefaultLoaders`, mirroring Console\n// `wireDefaultInspectors`) + D-2 (contract SSOT lives here in\n// `@forgeax/engine-types`, math-free, so `@forgeax/engine-runtime` only\n// depends on the interface, never reverse-imports a concrete loader)\n// - requirements core principle (third DIP instance after RHI / Console)\n// - charter P3 (structured failure) + P4 (consistent abstraction)\n//\n// A `Loader` is the runtime-side half of the import/load split: it turns an\n// already-imported internal artefact (a `.pack.json` payload, or fetched\n// bytes for texture / font) into an in-memory `Asset` POD. It stays pure of\n// the registry's bookkeeping — publication is the AssetRegistry's job,\n// never the loader's (plan-strategy D-2).\n//\n// Two dispatch shapes share this one contract (the asymmetry is intentional,\n// matching the two pre-existing AssetRegistry load paths the M1 refactor\n// converges; research Finding 1 + Finding 2):\n// (a) inline pack-payload kinds (mesh / scene / material /\n// skeleton / skin / animation-clip) parse synchronously and return\n// `Asset | undefined` (`undefined` = parse rejected, the caller maps it\n// to a structured `AssetError`).\n// (b) upstream-branch kinds (texture / font / equirect) fetch + decode\n// asynchronously and return a `Promise<LoaderAsyncResult>` carrying either\n// the produced `Asset` POD or a structured error.\n\n/**\n * Result envelope returned by the async branch of {@link Loader.load}\n * (texture / font). Mirrors the `Result<T, E>` shape used across the engine\n * (`.ok` discriminant) but is declared math-free here so\n * `@forgeax/engine-types` need not import `@forgeax/engine-rhi`. The error is\n * left as `unknown` so the runtime can surface its own\n * `AssetError | ImageError | RhiError` union without leaking those classes\n * into the types package (charter P4 — the runtime narrows; types stays\n * dependency-free).\n */\nexport type LoaderAsyncResult<P = Asset> =\n | { readonly ok: true; readonly value: P }\n | { readonly ok: false; readonly error: unknown };\n\n/**\n * Output of {@link Loader.load}. The synchronous arm returns `Asset` (parse\n * succeeded) or `undefined` (parse rejected); the asynchronous arm returns a\n * `Promise<LoaderAsyncResult>`.\n */\nexport type LoaderOutput<P = Asset> =\n | P\n | undefined\n | { readonly ok: false; readonly error: ParseErrorDetail }\n | Promise<LoaderAsyncResult<P>>;\n\n/**\n * Capabilities the host wires into a {@link Loader} at load time. A loader\n * receives this context so it never reaches back into AssetRegistry\n * internals (pipeline isolation, architecture-principles #4).\n *\n * Exactly three capabilities (plan-strategy D-3 rationale):\n * - `fetchBinary(url)` — fetch raw bytes for the artefact (texture import\n * `.bin`, `.hdr`, source image, font pack JSON).\n * - `resolveRef(guid)` — recursively resolve a referenced sub-asset GUID to\n * its registered handle id (font atlas / sampler). Returns the raw handle\n * number so the loader can stamp it into the produced POD; the runtime\n * performs the recursive typed load + registration underneath.\n * - `device` — opaque GPU device slot, present for future GPU-touching\n * loaders; current loaders register CPU PODs only and never touch it\n * (research Finding 3 — texture GPU upload is decoupled from load time via\n * the pull-model `GpuResourceStore`). Typed `unknown` so types stays\n * RHI-free.\n *\n * F21 (feat-20260621): the error-contextualization callback has been removed.\n */\nexport interface ParseErrorDetail {\n readonly localId: number;\n readonly component: string;\n readonly field: string;\n readonly index: number;\n readonly refsLength: number;\n}\n\n/**\n * Device texture-compression capabilities the transcode target selector reads\n * (feat-20260707 M5 / D-8, D-11).\n *\n * Three independent booleans mirror the WebGPU `texture-compression-{bc,etc2,\n * astc}` device features (and the `RhiCaps.textureCompression{Bc,Etc2,Astc}`\n * triple they are projected from — createRenderer does the one-line RhiCaps ->\n * TranscodeCaps projection). This shape is structurally identical to the codec\n * package's own `TranscodeCaps` (`@forgeax/engine-codec`): the codec keeps a\n * LOCAL copy on purpose (D-8 — codec is a pure, dependency-light transcode\n * library and must not take a `@forgeax/engine-types` edge just to name its\n * pure-function input). The runtime passes a value of this type straight into\n * `selectTranscodeTarget` by structural compatibility; there is exactly one\n * value threaded through `LoadContext`, so no fact is duplicated at runtime.\n */\nexport interface TranscodeCaps {\n readonly bc: boolean;\n readonly etc2: boolean;\n readonly astc: boolean;\n}\n\nexport interface LoadContext {\n /**\n * Fetch raw bytes for an asset artefact, with optional decompression.\n *\n * feat-20260706 M3 / w19: extended signature per D-2 — a `compression`\n * opt triggers the decompression gate inside the closure\n * (`@forgeax/engine-codec` lazy-init). `undefined` / `'none'` = E1\n * pass-through (backward-compat for legacy catalog rows).\n */\n fetchBinary(\n url: string,\n opts?: { readonly compression?: AssetCompression },\n ): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: unknown }\n >;\n resolveRef(\n guid: string,\n ): Promise<\n { readonly ok: true; readonly value: number } | { readonly ok: false; readonly error: unknown }\n >;\n /**\n * feat-20260613-material-paramschema-driven-binding M4 / w22 (D-5 graceful):\n * derive(paramSchema).textureFieldNames for the given material-shader id,\n * built from the registered shader's paramSchema. Used by materialLoader\n * to know which material value fields carry refs[] indices vs scalar values\n * (replacing the deleted hardcoded texture-field allowlist Set per AC-03).\n *\n * Returns `undefined` when the shader is not yet registered (the cross-\n * worktree shader-late-register path of plan R-4): the loader then falls\n * back to a graceful \"try every int paramValue as a refs index\" walk that\n * may misclassify scalar f32 fields whose value happens to land in\n * [0, refs.length); the extract layer (M4 / w23) catches mis-typed\n * handles via paramSchema validation and falls back to MISSING_TEXTURE_HANDLE.\n */\n getMaterialShaderTextureFieldNames?(shaderId: string): ReadonlySet<string> | undefined;\n /**\n * feat-20260707 M5 / w33 (D-11): device compression caps the texture / equirect\n * Basis arms feed to `selectTranscodeTarget` to pick a transcode target. Wired\n * by `createRenderer` from `RhiCaps` (D-8 one-line projection); a bare\n * AssetRegistry (test / headless path) defaults to all-false, which drives the\n * uncompressed `rgba8unorm` / `rgba16float` fallback (section 8 P3, AC-04).\n * Extends the single ctx input face rather than opening a new loader channel\n * (Pipeline Isolation — inputs declared explicitly).\n */\n readonly transcodeCaps: TranscodeCaps;\n readonly device: unknown;\n}\n\n/**\n * Runtime-side loader injected into the `LoaderRegistry`. One loader per\n * `asset.kind`; the registry dispatches `load` on the kind.\n *\n * `load` is pure of registry bookkeeping (no publication side effect); it only\n * produces the `Asset` POD (or a structured error / `undefined`). See the\n * module comment above for the sync vs async dispatch asymmetry.\n */\nexport interface Loader<P = Asset> {\n readonly kind: string;\n /** Optional Pack v2 dispatch that retains asset-local artifact bytes. */\n readonly loadPack?: (\n input: {\n readonly guid: string;\n readonly kind: string;\n readonly payload: Record<string, unknown>;\n readonly refs: readonly string[];\n readonly artifacts: Readonly<\n Record<\n string,\n {\n readonly descriptor: {\n readonly path: string;\n readonly mediaType: string;\n readonly assetCodec?: {\n readonly name: string;\n readonly container?: 'ktx2' | 'basis';\n readonly profile?: string;\n readonly version?: string;\n };\n };\n readonly bytes: Uint8Array;\n }\n >\n >;\n },\n ctx: LoadContext,\n ) => LoaderOutput<P>;\n load(\n payload: Record<string, unknown>,\n refs: readonly string[] | undefined,\n ctx: LoadContext,\n ): LoaderOutput<P>;\n}\n\nexport type {\n ImportContext,\n ImportDiagnostic,\n ImportDiagnosticLocation,\n ImportErrorCode,\n ImportErrorDetail,\n ImportedArtifactBody,\n ImportedAsset,\n Importer,\n ImportProduct,\n ImportResult,\n ImportSourceRange,\n ImportSubAsset,\n ImportTransport,\n SourceDependency,\n} from './import.js';\nexport {\n IMPORT_ERROR_HINTS,\n ImportError,\n} from './import.js';\n// === EngineMetrics contract SSOT (feat-20260705-runtime-tier2-decomposition M1 / w2, D-3) ===\n//\n// Decision anchors:\n// - plan-strategy D-3 (the 3-method EngineMetrics interface has zero type\n// dependencies; sinking it into @forgeax/engine-types makes types the SSOT\n// leaf. EngineMetricsImpl + createEngineMetrics stay in runtime.)\n// - research F5 (asset-registry.ts `import type { EngineMetrics }` was the\n// third reverse edge from assets cluster to runtime; type-only but the\n// project-reference layer still had to resolve it — relocating the contract\n// to the leaf removes the edge).\n//\n// The interface was originally defined in runtime/src/engine-metrics.ts. It is\n// promoted here so both @forgeax/engine-runtime and @forgeax/engine-assets-runtime\n// import the contract from a single entry point (charter F1).\n\n/**\n * Per-Renderer metrics counter API. Backed by a `Map<string, number>`; reads\n * return a frozen plain object so external mutation never leaks back into the\n * registry (D-5 + R-2 mutation-resistance).\n *\n * Three methods cover the full surface:\n *\n * const r = await createRenderer(canvas);\n * // ... renderer hits some nineslice runtime soft-warn\n * r.metrics.snapshot()['nineslice.scale-too-small']; // -> number | undefined\n *\n * r.metrics.increment('nineslice.scale-too-small'); // mutate counter\n * r.metrics.reset(); // drop all counters\n *\n * Callers can `for (const k in renderer.metrics.snapshot())` to enumerate\n * fired events without knowing the namespace ahead of time.\n *\n * @remarks Closed namespace (charter P5):\n * - `nineslice.scale-too-small`\n * - `nineslice.tile-needs-repeat-sampler`\n * - `render.instancing.foldedDraws`\n */\nexport interface EngineMetrics {\n /**\n * Bump the counter for `name` by 1. Counters start at 0 implicitly; the\n * first `increment(name)` lands a 1 in the snapshot. Names are free-form\n * strings (no prior registration), so feat-local namespaces (e.g.\n * `nineslice.*`) coexist without coordination.\n */\n increment(name: string): void;\n /**\n * Read all counters as an immutable `Readonly<Record<string, number>>`.\n * The returned object is frozen — external mutation throws in strict mode\n * and is silently ignored otherwise. Snapshot-then-mutate is decoupled\n * from the registry: a later `increment` does not retroactively alter the\n * already-returned snapshot.\n */\n snapshot(): Readonly<Record<string, number>>;\n /**\n * Drop every counter back to 0 (counter rows physically removed). Provided\n * for test isolation and Inspector reset workflows; production code on\n * the hot path never calls this.\n */\n reset(): void;\n}\n\n// inspector-client is Node-only (imports 'ws'); consume via\n// import { ... } from '@forgeax/engine-types/inspector-client'\n","import {\n type AssetDecoder,\n type AssetDecoderContribution,\n type AssetKind,\n type AssetLoadError,\n err,\n ok,\n type Result,\n type SceneAsset,\n type SceneEntity,\n type SceneInstanceMount,\n} from '@forgeax/engine-types';\n\nexport const sceneAssetKind: AssetKind<SceneAsset, 'scene'> = {\n kind: 'scene',\n} as AssetKind<SceneAsset, 'scene'>;\n\nfunction invalidScene(guid: string, reason: string): Result<SceneAsset, AssetLoadError> {\n return err({\n code: 'asset-package-invalid',\n expected: 'a scene payload with an entities array',\n hint: 'recook the SceneAsset and publish its complete envelope',\n detail: { guid, reason },\n });\n}\n\ntype SceneWireRefResult =\n | { readonly ok: true; readonly value: SceneAsset }\n | { readonly ok: false; readonly reason: string };\n\n// The Pack envelope owns refs[] while the decoded SceneAsset remains the\n// portable payload. Keep that wire-only fact beside the decoded object so the\n// World-local projection can interpret shared-field indices without putting a\n// component registry or World into the decoder contract.\nconst sceneWireRefs = new WeakMap<object, readonly string[]>();\n\n/** @internal Read the Pack refs[] retained for a decoded SceneAsset payload. */\nexport function sceneAssetWireRefs(asset: SceneAsset): readonly string[] | undefined {\n return sceneWireRefs.get(asset);\n}\n\nfunction resolveWireRef(\n refs: readonly string[],\n value: number,\n location: string,\n): { readonly ok: true; readonly value: string } | { readonly ok: false; readonly reason: string } {\n const guid = refs[value];\n if (!Number.isInteger(value) || value < 0 || guid === undefined) {\n return {\n ok: false,\n reason: `${location} references refs[${value}], but refs contains ${refs.length} entries`,\n };\n }\n return { ok: true, value: guid };\n}\n\nfunction resolveMounts(\n mounts: readonly SceneInstanceMount[] | undefined,\n refs: readonly string[],\n):\n | { readonly ok: true; readonly value: readonly SceneInstanceMount[] | undefined }\n | { readonly ok: false; readonly reason: string } {\n if (mounts === undefined) return { ok: true, value: undefined };\n const resolved: SceneInstanceMount[] = [];\n for (const mount of mounts) {\n if (typeof mount.source !== 'number' || !Number.isInteger(mount.source)) {\n resolved.push(mount);\n continue;\n }\n const ref = resolveWireRef(refs, mount.source, `mount ${mount.localId} source`);\n if (!ref.ok) return ref;\n resolved.push({ ...mount, source: ref.value });\n }\n return { ok: true, value: resolved };\n}\n\nfunction resolveSkinGuids(\n skinGuids: readonly (number | string)[] | undefined,\n refs: readonly string[],\n):\n | { readonly ok: true; readonly value: readonly string[] | undefined }\n | { readonly ok: false; readonly reason: string } {\n if (skinGuids === undefined) return { ok: true, value: undefined };\n const resolved: string[] = [];\n for (let index = 0; index < skinGuids.length; index += 1) {\n const value = skinGuids[index];\n if (typeof value === 'string') {\n resolved.push(value);\n continue;\n }\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n return { ok: false, reason: `skinGuids[${index}] is not a GUID or refs index` };\n }\n const ref = resolveWireRef(refs, value, `skinGuids[${index}]`);\n if (!ref.ok) return ref;\n resolved.push(ref.value);\n }\n return { ok: true, value: resolved };\n}\n\nfunction resolveSceneWireRefs(payload: SceneAsset, refs: readonly string[]): SceneWireRefResult {\n const entities: SceneEntity[] = [];\n for (const entity of payload.entities) {\n const components: Record<string, Record<string, unknown>> = {};\n for (const [componentName, rawFields] of Object.entries(entity.components)) {\n // Component schema lookup is World-local after the ECS core reduction.\n // The runtime projection owns the World-local schema and converts\n // authored GUID fields into World.sharedRefs handles. Keep this loader\n // boundary POD-only instead of consulting a removed process-global ECS\n // component registry.\n components[componentName] = { ...(rawFields as Record<string, unknown>) };\n }\n entities.push({ localId: entity.localId, components });\n }\n\n const mounts = resolveMounts(payload.mounts, refs);\n if (!mounts.ok) return mounts;\n const skinGuids = resolveSkinGuids(\n payload.skinGuids as readonly (number | string)[] | undefined,\n refs,\n );\n if (!skinGuids.ok) return skinGuids;\n\n return {\n ok: true,\n value: {\n kind: 'scene',\n entities,\n ...(mounts.value === undefined ? {} : { mounts: mounts.value }),\n ...(skinGuids.value === undefined ? {} : { skinGuids: skinGuids.value }),\n },\n };\n}\n\n/** Scene owns structural validation; World-local projection resolves shared refs. */\nexport const sceneAssetDecoder: AssetDecoder<SceneAsset> = {\n async decode({ envelope }): Promise<Result<SceneAsset, AssetLoadError>> {\n const payload = envelope.payload;\n if (payload.kind !== 'scene' || !Array.isArray(payload.entities)) {\n return invalidScene(envelope.guid, 'scene payload is missing entities');\n }\n const resolved = resolveSceneWireRefs(payload, envelope.refs);\n if (!resolved.ok) return invalidScene(envelope.guid, resolved.reason);\n sceneWireRefs.set(resolved.value, Object.freeze([...envelope.refs]));\n return ok(resolved.value);\n },\n};\n\nexport const sceneAssetContribution: AssetDecoderContribution<SceneAsset, 'scene'> = {\n kind: sceneAssetKind,\n decoder: sceneAssetDecoder,\n consumer: 'Scene',\n};\n","import type { World } from '@forgeax/engine-ecs';\nimport { componentSchema } from '@forgeax/engine-ecs/internal';\nimport { AssetGuid } from '@forgeax/engine-pack/guid';\nimport {\n type BuiltinAssetKind,\n type ComponentValuesMap,\n err,\n type Handle,\n type MeshAsset,\n type MountOverride,\n ok,\n type Result,\n type SceneAsset,\n type SceneEntity,\n type SceneInstanceMount,\n type SkinAsset,\n toShared,\n} from '@forgeax/engine-types';\nimport { worldSetSceneAssetResolver } from '../instances/scene-instances';\nimport { sceneAssetWireRefs } from './scene-decoder';\n\n/** World resource key for the scene projection's skin dependency view. */\nexport const SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY = 'SceneAssetSkinResolver';\n\n/** World resource key for mesh-owned material default resolution. */\nexport const SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY = 'SceneAssetMeshDefaultResolver';\n\n/**\n * Scene projection output consumed by the renderer's post-instantiate hook.\n * Scene owns the GUID-to-payload dependency view; ECS and Render only depend\n * on this narrow resolver shape.\n */\nexport interface SceneAssetSkinResolver {\n resolveSkinAsset(skeletonHandle: number): SkinAsset | undefined;\n}\n\n/**\n * Render-facing view of material defaults loaded while projecting a scene.\n * MeshAsset keeps these defaults as GUID facts; Render consumes World handles\n * without importing the AssetRegistry or teaching the scene component about\n * renderer policy.\n */\nexport interface SceneAssetMeshDefaultResolver {\n resolveMeshDefaultMaterial(guid: string): number | undefined;\n}\n\n/** Errors raised while projecting a decoded SceneAsset into one World. */\nexport type SceneAssetProjectionError =\n | {\n readonly code: 'scene-reference-target-unsupported';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly guid: string; readonly target: string; readonly field: string };\n }\n | {\n readonly code: 'scene-reference-kind-mismatch';\n readonly expected: string;\n readonly hint: string;\n readonly detail: {\n readonly guid: string;\n readonly target: string;\n readonly expectedKind: BuiltinAssetKind;\n readonly actualKind: string;\n readonly field: string;\n };\n }\n | {\n readonly code: 'scene-reference-cycle';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly cycle: readonly string[] };\n }\n | {\n readonly code: 'scene-reference-unresolved';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly source: string };\n };\n\n/** Loader seam supplied by the current App-owned AssetRegistry. */\nexport type SceneAssetReferenceLoader<E = unknown> = (\n guid: string,\n kind: BuiltinAssetKind,\n) => Promise<Result<unknown, E>>;\n\nconst ASSET_KIND_BY_TARGET: Readonly<Record<string, BuiltinAssetKind>> = Object.freeze({\n MeshAsset: 'mesh',\n TextureAsset: 'texture',\n EquirectAsset: 'equirect',\n SamplerAsset: 'sampler',\n MaterialAsset: 'material',\n SceneAsset: 'scene',\n AudioClipAsset: 'audio',\n SkinAsset: 'skin',\n SkeletonAsset: 'skeleton',\n AnimationClip: 'animation-clip',\n AnimationGraph: 'animation-graph',\n FontAsset: 'font',\n RenderPipelineAsset: 'render-pipeline',\n TilesetAsset: 'tileset',\n VideoAsset: 'video',\n ParticleEffectAsset: 'particle-effect',\n});\n\ntype ProjectError<E> = E | SceneAssetProjectionError;\n\ninterface ProjectionState<E> {\n readonly world: World;\n readonly load: SceneAssetReferenceLoader<E>;\n readonly assetHandles: Map<string, Handle<string, 'shared'>>;\n readonly sceneHandles: Map<string, Handle<'SceneAsset', 'shared'>>;\n readonly skinStore: SceneAssetProjectionStore;\n}\n\ninterface SceneAssetProjectionStore {\n readonly skeletonGuidByHandle: Map<number, string>;\n readonly skinByGuid: Map<string, SkinAsset>;\n readonly skinBySkeletonGuid: Map<string, SkinAsset>;\n readonly resolver: SceneAssetSkinResolver;\n readonly materialHandleByGuid: Map<string, number>;\n readonly meshDefaultResolver: SceneAssetMeshDefaultResolver;\n}\n\nconst projectionStores = new WeakMap<World, SceneAssetProjectionStore>();\n\nfunction projectionStoreFor(world: World): SceneAssetProjectionStore {\n const current = projectionStores.get(world);\n if (current !== undefined) return current;\n\n const skeletonGuidByHandle = new Map<number, string>();\n const skinByGuid = new Map<string, SkinAsset>();\n const skinBySkeletonGuid = new Map<string, SkinAsset>();\n const materialHandleByGuid = new Map<string, number>();\n const resolver: SceneAssetSkinResolver = {\n resolveSkinAsset(skeletonHandle) {\n const skeletonGuid = skeletonGuidByHandle.get(skeletonHandle);\n return skeletonGuid === undefined ? undefined : skinBySkeletonGuid.get(skeletonGuid);\n },\n };\n const meshDefaultResolver: SceneAssetMeshDefaultResolver = {\n resolveMeshDefaultMaterial(guid) {\n return materialHandleByGuid.get(guid.toLowerCase());\n },\n };\n const store = {\n skeletonGuidByHandle,\n skinByGuid,\n skinBySkeletonGuid,\n resolver,\n materialHandleByGuid,\n meshDefaultResolver,\n };\n projectionStores.set(world, store);\n world.insertResource(SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY, resolver);\n world.insertResource(SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY, meshDefaultResolver);\n return store;\n}\n\nfunction projectionError(\n code: SceneAssetProjectionError['code'],\n detail: SceneAssetProjectionError['detail'],\n): SceneAssetProjectionError {\n switch (code) {\n case 'scene-reference-target-unsupported':\n return {\n code,\n expected: 'every shared SceneAsset field target maps to a built-in Asset kind',\n hint: 'register the target in the AssetTagMap before projecting the scene',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-kind-mismatch':\n return {\n code,\n expected: 'the loaded payload kind matches the component shared-handle target',\n hint: 'repair the scene refs[] edge or republish the referenced asset',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-cycle':\n return {\n code,\n expected: 'an acyclic SceneAsset mount graph',\n hint: 'remove the circular mount.source reference and republish the scenes',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-unresolved':\n return {\n code,\n expected: 'a mount.source GUID that is loadable by the current AssetRegistry',\n hint: 'publish the child SceneAsset before projecting its parent',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n }\n}\n\nfunction isPayloadOfKind(\n value: unknown,\n kind: BuiltinAssetKind,\n): value is { readonly kind: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { readonly kind?: unknown }).kind === kind\n );\n}\n\nfunction sharedTarget(fieldType: unknown): string | undefined {\n if (typeof fieldType !== 'string') return undefined;\n const match = /^shared<([^>]+)>$/.exec(fieldType);\n return match?.[1];\n}\n\nfunction schemaFieldType(field: unknown): unknown {\n if (typeof field === 'object' && field !== null && 'type' in field) {\n return (field as { readonly type?: unknown }).type;\n }\n return field;\n}\n\nfunction sharedArrayTarget(fieldType: unknown): string | undefined {\n if (typeof fieldType !== 'string') return undefined;\n const match = /^array<shared<([^>]+)>(?:,\\s*\\d+)? *>$/.exec(fieldType);\n return match?.[1];\n}\n\nfunction resolveWireValue<E>(\n value: unknown,\n refs: readonly string[] | undefined,\n location: string,\n): Result<unknown, ProjectError<E>> {\n if (refs === undefined || typeof value !== 'number') return ok(value);\n const guid = refs[value];\n if (!Number.isInteger(value) || guid === undefined) {\n return err(\n projectionError('scene-reference-unresolved', {\n source: `${location} refs[${value}]`,\n }),\n );\n }\n return ok(guid);\n}\n\nasync function projectSharedValue<E>(\n state: ProjectionState<E>,\n value: unknown,\n target: string,\n field: string,\n): Promise<Result<unknown, ProjectError<E>>> {\n if (typeof value !== 'string') {\n if (target === 'MeshAsset' && typeof value === 'number') {\n const resolved = state.world.sharedRefs.resolve(toShared<'MeshAsset'>(value));\n if (resolved.ok && isPayloadOfKind(resolved.value, 'mesh')) {\n const meshDefaults = await projectMeshDefaults(state, resolved.value as MeshAsset, field);\n if (!meshDefaults.ok) return meshDefaults;\n }\n }\n return ok(value);\n }\n const kind = ASSET_KIND_BY_TARGET[target];\n if (kind === undefined) {\n return err(\n projectionError('scene-reference-target-unsupported', {\n guid: value,\n target,\n field,\n }),\n );\n }\n\n const cacheKey = `${target}:${value.toLowerCase()}`;\n const cached = state.assetHandles.get(cacheKey);\n if (cached !== undefined) {\n if (target === 'SkeletonAsset') {\n state.skinStore.skeletonGuidByHandle.set(Number(cached), value.toLowerCase());\n }\n return ok(cached);\n }\n\n const loaded = await state.load(value, kind);\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, kind)) {\n return err(\n projectionError('scene-reference-kind-mismatch', {\n guid: value,\n target,\n expectedKind: kind,\n actualKind:\n typeof loaded.value === 'object' && loaded.value !== null && 'kind' in loaded.value\n ? String((loaded.value as { readonly kind: unknown }).kind)\n : typeof loaded.value,\n field,\n }),\n );\n }\n\n if (target === 'MeshAsset') {\n const meshDefaults = await projectMeshDefaults(state, loaded.value as MeshAsset, field);\n if (!meshDefaults.ok) return meshDefaults;\n }\n\n const handle = state.world.allocSharedRef(target, loaded.value);\n state.assetHandles.set(cacheKey, handle);\n if (target === 'SkeletonAsset') {\n state.skinStore.skeletonGuidByHandle.set(Number(handle), value.toLowerCase());\n }\n return ok(handle);\n}\n\nasync function projectMeshDefaults<E>(\n state: ProjectionState<E>,\n mesh: MeshAsset,\n field: string,\n): Promise<Result<void, ProjectError<E>>> {\n if (!Array.isArray(mesh.materialSlots)) return ok(undefined);\n for (let slotIndex = 0; slotIndex < mesh.materialSlots.length; slotIndex += 1) {\n const defaultMaterial = mesh.materialSlots[slotIndex]?.defaultMaterial;\n if (defaultMaterial === undefined) continue;\n const guid = AssetGuid.format(defaultMaterial);\n const projected = await projectSharedValue(\n state,\n guid,\n 'MaterialAsset',\n `${field}.materialSlots[${slotIndex}].defaultMaterial`,\n );\n if (!projected.ok) return projected;\n state.skinStore.materialHandleByGuid.set(guid, Number(projected.value));\n }\n return ok(undefined);\n}\n\nasync function projectSkinDependencies<E>(\n state: ProjectionState<E>,\n asset: SceneAsset,\n): Promise<Result<void, ProjectError<E>>> {\n for (let index = 0; index < (asset.skinGuids?.length ?? 0); index += 1) {\n const guid = asset.skinGuids?.[index];\n if (guid === undefined) continue;\n const guidKey = guid.toLowerCase();\n if (state.skinStore.skinByGuid.has(guidKey)) continue;\n\n const loaded = await state.load(guid, 'skin');\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, 'skin')) {\n return err(\n projectionError('scene-reference-kind-mismatch', {\n guid,\n target: 'SkinAsset',\n expectedKind: 'skin',\n actualKind:\n typeof loaded.value === 'object' && loaded.value !== null && 'kind' in loaded.value\n ? String((loaded.value as { readonly kind: unknown }).kind)\n : typeof loaded.value,\n field: `scene.skinGuids[${index}]`,\n }),\n );\n }\n const skin = loaded.value as SkinAsset;\n state.skinStore.skinByGuid.set(guidKey, skin);\n state.skinStore.skinBySkeletonGuid.set(skin.skeletonGuid.toLowerCase(), skin);\n }\n return ok(undefined);\n}\n\nasync function projectFields<E>(\n state: ProjectionState<E>,\n componentName: string,\n rawFields: Record<string, unknown>,\n location: string,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<Record<string, unknown>, ProjectError<E>>> {\n const component = state.world.components.resolve(componentName);\n if (component === undefined) return ok({ ...rawFields });\n\n const fields: Record<string, unknown> = { ...rawFields };\n for (const [fieldName, value] of Object.entries(rawFields)) {\n const fieldType = schemaFieldType(componentSchema(component)[fieldName]);\n const target = sharedTarget(fieldType);\n if (target !== undefined) {\n const wireValue = await resolveWireValue<E>(\n value,\n wireRefs,\n `${location}.${componentName}.${fieldName}`,\n );\n if (!wireValue.ok) return wireValue;\n const projected = await projectSharedValue(\n state,\n wireValue.value,\n target,\n `${location}.${componentName}.${fieldName}`,\n );\n if (!projected.ok) return projected;\n fields[fieldName] = projected.value;\n continue;\n }\n\n const arrayTarget = sharedArrayTarget(fieldType);\n if (arrayTarget === undefined || !Array.isArray(value)) continue;\n const projectedValues: unknown[] = [];\n for (let index = 0; index < value.length; index += 1) {\n const wireValue = await resolveWireValue<E>(\n value[index],\n wireRefs,\n `${location}.${componentName}.${fieldName}[${index}]`,\n );\n if (!wireValue.ok) return wireValue;\n const projected = await projectSharedValue(\n state,\n wireValue.value,\n arrayTarget,\n `${location}.${componentName}.${fieldName}[${index}]`,\n );\n if (!projected.ok) return projected;\n projectedValues.push(projected.value);\n }\n fields[fieldName] = projectedValues;\n }\n return ok(fields);\n}\n\nasync function projectComponents<E>(\n state: ProjectionState<E>,\n components: Partial<ComponentValuesMap>,\n location: string,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<Partial<ComponentValuesMap>, ProjectError<E>>> {\n const projected: Record<string, Record<string, unknown>> = {};\n for (const [componentName, rawFields] of Object.entries(components)) {\n if (typeof rawFields !== 'object' || rawFields === null || Array.isArray(rawFields)) continue;\n const fields = await projectFields(\n state,\n componentName,\n rawFields as Record<string, unknown>,\n location,\n wireRefs,\n );\n if (!fields.ok) return fields;\n projected[componentName] = fields.value;\n }\n return ok(projected);\n}\n\nasync function projectOverride<E>(\n state: ProjectionState<E>,\n override: MountOverride,\n index: number,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<MountOverride, ProjectError<E>>> {\n const component = state.world.components.resolve(override.comp);\n if (component === undefined) return ok(override);\n\n if (override.field !== undefined) {\n const fieldType = schemaFieldType(componentSchema(component)[override.field]);\n const target = sharedTarget(fieldType);\n if (target !== undefined) {\n const wireValue = await resolveWireValue<E>(\n override.value,\n wireRefs,\n `mount override ${index}.${override.comp}.${override.field}`,\n );\n if (!wireValue.ok) return wireValue;\n const value = await projectSharedValue(\n state,\n wireValue.value,\n target,\n `mount override ${index}.${override.comp}.${override.field}`,\n );\n if (!value.ok) return value;\n return ok({ ...override, value: value.value });\n }\n const arrayTarget = sharedArrayTarget(fieldType);\n if (arrayTarget !== undefined && Array.isArray(override.value)) {\n const values: unknown[] = [];\n for (let element = 0; element < override.value.length; element += 1) {\n const wireValue = await resolveWireValue<E>(\n override.value[element],\n wireRefs,\n `mount override ${index}.${override.comp}.${override.field}[${element}]`,\n );\n if (!wireValue.ok) return wireValue;\n const value = await projectSharedValue(\n state,\n wireValue.value,\n arrayTarget,\n `mount override ${index}.${override.comp}.${override.field}[${element}]`,\n );\n if (!value.ok) return value;\n values.push(value.value);\n }\n return ok({ ...override, value: values });\n }\n return ok(override);\n }\n\n if (\n typeof override.value !== 'object' ||\n override.value === null ||\n Array.isArray(override.value)\n ) {\n return ok(override);\n }\n const value = await projectFields(\n state,\n override.comp,\n override.value as Record<string, unknown>,\n `mount override ${index}`,\n wireRefs,\n );\n if (!value.ok) return value;\n return ok({ ...override, value: value.value });\n}\n\nasync function projectMount<E>(\n state: ProjectionState<E>,\n mount: SceneInstanceMount,\n index: number,\n visiting: Set<string>,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<SceneInstanceMount, ProjectError<E>>> {\n const components =\n mount.components === undefined\n ? undefined\n : await projectComponents(state, mount.components, `mount ${index}`, wireRefs);\n if (components !== undefined && !components.ok) return components;\n\n const overrides: MountOverride[] = [];\n for (let overrideIndex = 0; overrideIndex < (mount.overrides?.length ?? 0); overrideIndex += 1) {\n const override = mount.overrides?.[overrideIndex];\n if (override === undefined) continue;\n const projected = await projectOverride(state, override, overrideIndex, wireRefs);\n if (!projected.ok) return projected;\n overrides.push(projected.value);\n }\n\n if (typeof mount.source !== 'string') {\n return ok({\n ...mount,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n }\n\n const sourceKey = mount.source.toLowerCase();\n const cached = state.sceneHandles.get(sourceKey);\n if (cached !== undefined) {\n return ok({\n ...mount,\n source: cached as unknown as number,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n }\n if (visiting.has(sourceKey)) {\n return err(\n projectionError('scene-reference-cycle', {\n cycle: [...visiting, sourceKey],\n }),\n );\n }\n\n const loaded = await state.load(mount.source, 'scene');\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, 'scene')) {\n return err(projectionError('scene-reference-unresolved', { source: mount.source }));\n }\n\n visiting.add(sourceKey);\n const projectedChild = await projectScene(state, loaded.value as SceneAsset, visiting);\n visiting.delete(sourceKey);\n if (!projectedChild.ok) return projectedChild;\n\n const childHandle = state.world.allocSharedRef('SceneAsset', projectedChild.value);\n state.sceneHandles.set(sourceKey, childHandle);\n return ok({\n ...mount,\n source: childHandle as unknown as number,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n}\n\nasync function projectScene<E>(\n state: ProjectionState<E>,\n asset: SceneAsset,\n visiting: Set<string>,\n): Promise<Result<SceneAsset, ProjectError<E>>> {\n const skins = await projectSkinDependencies(state, asset);\n if (!skins.ok) return skins;\n\n const entities: SceneEntity[] = [];\n const wireRefs = sceneAssetWireRefs(asset);\n for (const entity of asset.entities) {\n const components = await projectComponents(\n state,\n entity.components,\n `entity ${entity.localId as number}`,\n wireRefs,\n );\n if (!components.ok) return components;\n entities.push({ localId: entity.localId, components: components.value });\n }\n\n const mounts: SceneInstanceMount[] = [];\n for (let index = 0; index < (asset.mounts?.length ?? 0); index += 1) {\n const mount = asset.mounts?.[index];\n if (mount === undefined) continue;\n const projected = await projectMount(state, mount, index, visiting, wireRefs);\n if (!projected.ok) return projected;\n mounts.push(projected.value);\n }\n\n return ok({\n kind: 'scene',\n entities,\n ...(asset.mounts === undefined ? {} : { mounts }),\n ...(asset.skinGuids === undefined ? {} : { skinGuids: [...asset.skinGuids] }),\n });\n}\n\n/**\n * Convert decoded GUID fields and nested SceneAsset mounts into references\n * owned by one World. The AssetRegistry remains a loader; this World-facing\n * projection belongs to Scene and is intentionally explicit at each consumer.\n */\nexport async function projectSceneAsset<E = unknown>(\n world: World,\n asset: SceneAsset,\n load: SceneAssetReferenceLoader<E>,\n): Promise<Result<SceneAsset, ProjectError<E>>> {\n const skinStore = projectionStoreFor(world);\n worldSetSceneAssetResolver(world, (source) => {\n if (typeof source === 'number') return ok(toShared<'SceneAsset'>(source));\n return err(projectionError('scene-reference-unresolved', { source }));\n });\n return projectScene(\n {\n world,\n load,\n assetHandles: new Map(),\n sceneHandles: new Map(),\n skinStore,\n },\n asset,\n new Set(),\n );\n}\n","// @forgeax/engine-scene — scene instantiation and instance-state subsystem.\n\nimport {\n type Component,\n type ComponentData,\n type ComponentSchema,\n type EcsError,\n ENTITY_NULL_RAW,\n type EntityHandle,\n type InputShapeOf,\n type ShapeOf,\n type World,\n} from '@forgeax/engine-ecs';\nimport { classifyEntityField, remapEntityFieldValue } from '@forgeax/engine-ecs/externalization';\nimport { componentSchema } from '@forgeax/engine-ecs/internal';\nimport { fillComponentDefaults, StaleEntityError } from '@forgeax/engine-ecs/projection';\nimport type {\n Handle,\n LocalEntityId,\n MountOverride,\n PackErrorCode,\n PackErrorDetail,\n SceneAsset,\n SceneInstanceMount,\n} from '@forgeax/engine-types';\nimport {\n err,\n ok,\n PACK_ERROR_HINTS,\n type Result,\n toUnique,\n unwrapHandle,\n} from '@forgeax/engine-types';\nimport { ComponentNotDefinedError } from '../errors';\n\nconst entityIndex = (entity: EntityHandle): number => (entity as number) & 0x00ffffff;\nconst entityGeneration = (entity: EntityHandle): number => ((entity as number) >>> 24) & 0xff;\n\n/**\n * C-R2 (feat-20260622-s5 / studio-issues): one structured, non-fatal record of\n * a SceneAsset payload field that did NOT match the target component's schema.\n *\n * Scene data is loader-fed and may carry a stale / deprecated / typo'd field\n * (an editor renames a field, an old `.pack.json` lags). `worldInstantiateScene`\n * does NOT blank the whole scene over one such field (#478 lesson: a\n * prod-silent strip re-introduced an invisible-entity class) and does NOT abort\n * fatally. Instead it skips the unknown key (no write, no input mutation) and\n * surfaces this record on the success value's `diagnostics[]` — observable in\n * production (NOT NODE_ENV-gated), consumed by property access (no string parse):\n *\n * const r = worldInstantiateScene(world, handle);\n * if (r.ok) for (const d of r.value.diagnostics)\n * console.warn(`unknown field ${d.component}.${d.field} on localId ${d.localId}`);\n *\n * Direct `world.spawn` / `world.addComponent` / `Commands.spawn` stay fail-fast\n * with `SpawnDataUnknownFieldError` — those are explicit API calls where a typo\n * is a programming error, not loader-fed data.\n */\nexport type SceneInstantiateDiagnostic = {\n /** Component name (schema key) the unknown field appeared under. */\n readonly component: string;\n /** The offending field name not declared in the component schema. */\n readonly field: string;\n /** LocalEntityId (within its owning SceneAsset) of the carrying entity. */\n readonly localId: number;\n};\n\n/**\n * Success value of `worldInstantiateScene`. `root` is the synthetic scene-root\n * EntityHandle (carries `SceneInstance`); `diagnostics` is the (possibly empty)\n * list of non-fatal unknown-field records aggregated across this scene and every\n * recursively mounted sub-scene (C-R2). Empty array = no diagnostics.\n */\nexport type SceneInstantiateOk = {\n readonly root: EntityHandle;\n readonly diagnostics: readonly SceneInstantiateDiagnostic[];\n};\n\n/**\n * Success value of `worldInstantiateSceneFlat` — the \"edit the scene itself\"\n * primitive. Unlike `instantiateScene`, NO synthetic SceneInstance root is\n * minted and NO `ChildOf` is forced onto top-level members: the scene's own\n * entities become plain top-level world entities whose hierarchy is exactly\n * their authored `ChildOf` (an entity with no `ChildOf` stays a root). `roots`\n * is the set of those top-level handles (own rootless entities + top-level\n * mount carriers). Nested prefabs inside the scene STILL materialise as their\n * own SceneInstance anchors (charter P4: instance == entity-with-SceneInstance)\n * — only THIS scene is flat.\n */\nexport type SceneInstantiateFlatOk = {\n readonly roots: EntityHandle[];\n /**\n * All mount carrier entities spawned while flattening this scene. These are\n * separate from `roots`: carriers with an authored parent are not roots,\n * but still delimit a nested prefab subtree for post-spawn hooks.\n */\n readonly mountEntities: EntityHandle[];\n readonly diagnostics: readonly SceneInstantiateDiagnostic[];\n};\n\n/**\n * @internal Intermediate produced by `_spawnSceneMembers` and consumed by both\n * the anchor finisher (`_instantiateSceneAsset`) and the flat finisher\n * (`_instantiateSceneAssetFlat`). Holds everything the shared member-spawn\n * (mounts recursion + own-entity spawn + deferred owned-parent wiring) computes,\n * before either finisher decides whether to wrap the members in a synthetic\n * SceneInstance root.\n */\nexport interface SceneMembersSpawn {\n /** LocalEntityId → live Entity u32 (ENTITY_NULL_RAW for unspawned slots). */\n readonly mapping: Uint32Array;\n /** Reverse map live Entity → LocalEntityId for override / detach bookkeeping. */\n readonly entityToLocalId: Map<EntityHandle, LocalEntityId>;\n /** Own entities that carried no `ChildOf` — the scene's authored top-level roots. */\n readonly rootEntities: EntityHandle[];\n /** Mount carriers whose `mount.parent === undefined` (default-parented). */\n readonly mountEntitiesNeedingRootParent: EntityHandle[];\n /** Every mount carrier spawned by this scene, including explicitly parented carriers. */\n readonly mountEntities: EntityHandle[];\n /**\n * The child anchor and mapping for each mount. Flat scene opening has no\n * outer SceneInstance state to own parent-namespace mount overrides, so it\n * records those overrides on this child anchor after the shared spawn pass.\n */\n readonly mountInstances: readonly {\n readonly mount: SceneInstanceMount;\n readonly root: EntityHandle;\n readonly mapping: Uint32Array;\n }[];\n /** `entities.length + mounts + Σ memberCount`, captured at instantiate-time. */\n readonly totalSlots: number;\n}\n\nexport type SceneAssetResolver = (\n source: number | string,\n parentHandle: Handle<'SceneAsset', 'shared'>,\n) => Result<Handle<'SceneAsset', 'shared'>, unknown>;\n\ninterface SceneWorldState {\n resolver: SceneAssetResolver | null;\n readonly statePayloads: Map<number, unknown>;\n instantiateHook: SceneInstantiateHook | null;\n}\n\nexport type SceneInstantiateHook = (\n world: World,\n root: EntityHandle,\n) => { readonly ok: true } | { readonly ok: false; readonly error: unknown };\n\nconst sceneWorldStates = new WeakMap<World, SceneWorldState>();\n\nfunction sceneWorldState(world: World): SceneWorldState {\n const current = sceneWorldStates.get(world);\n if (current !== undefined) return current;\n const created = {\n resolver: null,\n statePayloads: new Map<number, unknown>(),\n instantiateHook: null,\n };\n sceneWorldStates.set(world, created);\n return created;\n}\n\n/** @internal */\nexport function worldSetSceneAssetResolver(world: World, resolver: SceneAssetResolver): void {\n sceneWorldState(world).resolver = resolver;\n}\n\n/** @internal */\nexport function worldGetSceneAssetResolver(world: World): SceneAssetResolver | null {\n return sceneWorldState(world).resolver;\n}\n\n/** @internal Install the owner post-instantiate boundary for one World. */\nexport function worldSetSceneInstantiateHook(\n world: World,\n hook: SceneInstantiateHook | null,\n): void {\n sceneWorldState(world).instantiateHook = hook;\n}\n\n/**\n * Materialise a SceneAsset (and any nested SceneAsset references via\n * `mounts[]`) into live entities. Returns the synthetic root Entity that\n * carries the `SceneInstance` ECS component (charter P4: instance ==\n * entity-with-SceneInstance).\n *\n * Recursion path is closed inside `_instantiateSceneRec(handle, parent,\n * stack)` (D-3); cycle detection is fail-fast `pack-cyclic-reference +\n * detail.kind:'mount-asset'` (D-1 mirror, plan-strategy §D-3). The\n * caller-supplied `parent` flows to the synthetic root's `ChildOf` so the\n * full sub-tree attaches under the AI user's host entity.\n *\n * @example\n * const r = worldInstantiateScene(world, handle);\n * if (!r.ok) return r;\n * const { root, diagnostics } = r.value;\n * for (const d of diagnostics) // C-R2: unknown-field records, non-fatal\n * console.warn(`unknown field ${d.component}.${d.field} on localId ${d.localId}`);\n * const inst = world.get(root, SceneInstance).value;\n * const member = inst.mapping[0]; // first member entity\n */\nexport function worldInstantiateScene(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n parent?: EntityHandle,\n): Result<SceneInstantiateOk, EcsError> {\n const stack = new Set<number>();\n // C-R2: collect non-fatal unknown-field diagnostics across this scene and\n // every recursively mounted sub-scene. The internal recursion writes into\n // this accumulator; only the public entry packages it onto the success value.\n const diagnostics: SceneInstantiateDiagnostic[] = [];\n const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);\n if (!r.ok) return r;\n const hook = sceneWorldState(world).instantiateHook;\n if (hook !== null) {\n const hooked = hook(world, r.value);\n if (!hooked.ok) {\n worldDespawnScene(world, r.value);\n return err(hooked.error as EcsError);\n }\n }\n return ok({ root: r.value, diagnostics });\n}\n/**\n * Materialise a SceneAsset FLAT — the \"edit the scene itself\" primitive.\n * Unlike `instantiateScene`, this mints NO synthetic SceneInstance root and\n * forces NO `ChildOf` onto top-level members: the scene's own entities become\n * plain top-level world entities whose hierarchy is exactly their authored\n * `ChildOf` (an entity with no `ChildOf` is a root). Use this to OPEN a scene\n * for editing; use `instantiateScene` (anchor) at runtime / for nested\n * prefabs where an instance boundary + override isolation is wanted.\n *\n * Nested prefabs referenced via `mounts[]` STILL materialise as their own\n * SceneInstance anchors (charter P4 preserved) — only THIS top scene is flat.\n *\n * @example\n * const r = worldInstantiateSceneFlat(world, handle);\n * if (!r.ok) return r;\n * const { roots, diagnostics } = r.value; // roots = top-level handles\n */\nexport function worldInstantiateSceneFlat(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n): Result<SceneInstantiateFlatOk, EcsError> {\n const stack = new Set<number>();\n const diagnostics: SceneInstantiateDiagnostic[] = [];\n const handleKey = unwrapHandle(handle);\n const resolved = worldResolveSceneAsset(world, handle);\n if (!resolved.ok) return resolved;\n stack.add(handleKey);\n let r: Result<{ roots: EntityHandle[]; mountEntities: EntityHandle[] }, EcsError>;\n try {\n r = worldInstantiateSceneAssetFlat(world, handle, resolved.value, stack, diagnostics);\n } finally {\n stack.delete(handleKey);\n }\n if (!r.ok) return r;\n return ok({ ...r.value, diagnostics });\n}\n/**\n * @internal Recursive helper carrying the cycle-detection stack. Sugar /\n * other public callers must not see this mechanic — use `instantiateScene`\n * (D-3 / charter P1).\n */\nexport function worldInstantiateSceneRec(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n parent: EntityHandle | undefined,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const handleKey = unwrapHandle(handle);\n if (stack.has(handleKey)) {\n const cycleArr: string[] = [];\n for (const k of stack) cycleArr.push(String(k));\n cycleArr.push(String(handleKey));\n const detail: PackErrorDetail = {\n code: 'pack-cyclic-reference',\n kind: 'mount-asset',\n cycle: cycleArr,\n };\n return err({\n code: 'pack-cyclic-reference' as PackErrorCode,\n expected: 'acyclic SceneAsset mount graph',\n hint: PACK_ERROR_HINTS['pack-cyclic-reference'],\n detail,\n } as unknown as EcsError);\n }\n const resolved = worldResolveSceneAsset(world, handle);\n if (!resolved.ok) return resolved;\n const asset = resolved.value;\n stack.add(handleKey);\n try {\n return worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics);\n } finally {\n stack.delete(handleKey);\n }\n}\n/**\n * @internal Resolve a SceneAsset handle through the SharedRefStore.\n * The handle u32 is the SharedRefStore slot id (`world.allocSharedRef\n * ('SceneAsset', asset)` is the producer; rc starts at 1, the SceneInstance\n * spawn retains to rc=2 in M4 / w13). Errors propagate as EcsError so the\n * instantiateScene chain returns a single closed union.\n */\nexport function worldResolveSceneAsset(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n): Result<SceneAsset, EcsError> {\n const r = world.sharedRefs.resolve(handle);\n if (!r.ok) {\n return err(r.error as unknown as EcsError);\n }\n return ok(r.value as SceneAsset);\n}\n/**\n * @internal Spawn one SceneAsset's members — the shared body of both scene\n * finishers. Recurses into `mounts[]` (each nested prefab becomes its own\n * SceneInstance anchor), spawns `entities[]` honouring their authored\n * `ChildOf`, and wires deferred owned-parent mount edges. Does NOT create a\n * synthetic root or force any `ChildOf` — that is the caller's (finisher's)\n * job. `_instantiateSceneRec` owns cycle bookkeeping.\n */\nexport function worldSpawnSceneMembers(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<SceneMembersSpawn, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const childOfToken = world.components.resolve('ChildOf');\n // ChildOf is optional — only needed if the asset declares ChildOf or a\n // caller-supplied parent must be wired. If absent and we need it, we\n // fail-fast at the wiring site below.\n\n const ownEntities = asset.entities;\n const ownMounts = asset.mounts ?? [];\n const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);\n const countBaseline = ownEntities.length + ownMounts.length + memberSum;\n // C-R1 (studio-issues #6): mapping table must be sized to maxLocalId+1,\n // not to the entity count. An editor scene may have non-contiguous\n // localIds (deleted entities leave gaps); sizing to count means any\n // localId >= count is a silent Uint32Array OOB no-op -> entity spawns\n // but is unreachable by localId -> users report \"character can't move\".\n // Take the max of count-baseline and id-range so both packed and\n // sparse scenes work without over-allocation in the common case.\n let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId as unknown as number), -1);\n for (const mount of ownMounts) {\n maxLocalId = Math.max(maxLocalId, mount.localId as unknown as number);\n const last = (mount.memberFirst as unknown as number) + mount.memberCount - 1;\n maxLocalId = Math.max(maxLocalId, last);\n }\n const totalSlots = Math.max(countBaseline, maxLocalId + 1);\n\n // R2/Bonus: namespace-overlap fail-fast (AC-05 /\n // pack-mount-localid-overlap). Each LocalEntityId in\n // [0, totalSlots) must be claimed by exactly one of:\n // - entities[i].localId\n // - mounts[i].localId\n // - mounts[i] window slot (memberFirst .. memberFirst+memberCount-1)\n // Overlap or duplicate claim => fail-fast with the offending localIds\n // and human-readable origin labels.\n {\n const claims = new Map<number, string>();\n const overlapLids = new Set<number>();\n const overlapSources: string[] = [];\n const claim = (lid: number, src: string): void => {\n const prior = claims.get(lid);\n if (prior !== undefined) {\n if (!overlapLids.has(lid)) {\n overlapLids.add(lid);\n overlapSources.push(prior);\n overlapSources.push(src);\n } else {\n overlapSources.push(src);\n }\n return;\n }\n claims.set(lid, src);\n };\n for (const ent of ownEntities) {\n claim(ent.localId as unknown as number, `entities[${ent.localId as unknown as number}]`);\n }\n for (const mount of ownMounts) {\n const mLid = mount.localId as unknown as number;\n claim(mLid, `mount[${mLid}]`);\n const first = mount.memberFirst as unknown as number;\n for (let k = 0; k < mount.memberCount; k += 1) {\n claim(first + k, `mount[${mLid}].member[${k}]`);\n }\n }\n if (overlapLids.size > 0) {\n const overlapping = Array.from(overlapLids).sort((a, b) => a - b);\n return err({\n code: 'pack-mount-localid-overlap' as PackErrorCode,\n expected: 'each LocalEntityId claimed by exactly one entity or mount slot',\n hint: PACK_ERROR_HINTS['pack-mount-localid-overlap'],\n detail: {\n code: 'pack-mount-localid-overlap',\n overlapping,\n sources: overlapSources,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n\n // Slot table: indexed by LocalEntityId; populated as entities / mounts /\n // members are spawned. mapping[localId] = encoded Entity u32. Unspawned\n // slots hold ENTITY_NULL_RAW (0xffffffff) — NOT 0, because a fresh World's\n // first spawn encodes to gen=0+idx=0=raw 0, which is a valid Entity. The\n // remap path in `_buildSceneEntityComponentDatas` distinguishes the two\n // (live=ENTITY_NULL_RAW => parent unspawned at remap time => surface as\n // null sentinel; live=any other u32 => valid live Entity, including 0).\n const mapping = new Uint32Array(totalSlots).fill(ENTITY_NULL_RAW);\n const entityToLocalId = new Map<EntityHandle, LocalEntityId>();\n const rootEntities: EntityHandle[] = [];\n const mountEntities: EntityHandle[] = [];\n // R2/B-1: mount entities whose `mount.parent === undefined` need their\n // ChildOf wired to the outer synthetic root (this scene's root). Step 5\n // does the wiring once the synthetic root entity is materialised; we\n // collect them here in step 1.\n const mountEntitiesNeedingRootParent: EntityHandle[] = [];\n // D-8 (feat-20260707): mount entities whose `mount.parent` points at an\n // OWNED entity slot are wired AFTER step 2 spawns the owned entities —\n // mounts are processed first (step 1), so the owned parent slot is still\n // ENTITY_NULL_RAW at mount-processing time. Same deferred-wiring shape as\n // mountEntitiesNeedingRootParent: register [mountEntity, parentSlot] here,\n // wire ChildOf once the slot is live. Without this the edge was silently\n // dropped, and the mount carrier stayed unreachable from its owned parent.\n const mountEntitiesNeedingDeferredParent: Array<[EntityHandle, number]> = [];\n const mountInstances: Array<{\n readonly mount: SceneInstanceMount;\n readonly root: EntityHandle;\n readonly mapping: Uint32Array;\n }> = [];\n\n // 1. Recurse into mounts[] FIRST so the mount-window slots\n // (`mount.localId` + `[memberFirst, memberFirst+memberCount)`) are\n // populated before any owned entity tries to remap a LocalEntityId\n // pointing into the mount window (AC-24 cross-boundary reference).\n for (const mount of ownMounts) {\n // R2/B-3 + R2/B-4: validate overrides BEFORE child resolution so a\n // malformed override fails fast without observable side-effects.\n const overrideValidationRes = worldValidateMountOverrides(world, mount);\n if (!overrideValidationRes.ok) {\n return overrideValidationRes;\n }\n\n // Spawn the mount entity (carries mount.components).\n const mountLid = mount.localId as unknown as number;\n const mountSpawnRes = worldSpawnMountEntity(world, mount, mapping, diagnostics);\n if (!mountSpawnRes.ok) return mountSpawnRes;\n const mountEntity = mountSpawnRes.value;\n mountEntities.push(mountEntity);\n mapping[mountLid] = mountEntity as unknown as number;\n\n // Resolve mount.source -> child SceneAsset handle.\n const childHandleRes = worldResolveMountSource(world, mount.source, handle);\n if (!childHandleRes.ok) return childHandleRes;\n const childHandle = childHandleRes.value;\n\n // Recursively instantiate the child. Its synthetic root attaches as a\n // child of the mount entity. The child writes its own unknown-field\n // diagnostics into the SAME accumulator, so they bubble to the top-level\n // instantiateScene result (C-R2 recursive aggregation).\n const childRes = worldInstantiateSceneRec(world, childHandle, mountEntity, stack, diagnostics);\n if (!childRes.ok) return childRes;\n\n // R2/B-2: cross-check mount.memberCount === child.totalSlots BEFORE\n // copying the mount window. The child SceneInstance.mapping length is\n // the authoritative `totalSlots` of the child. AC-04 / requirements\n // S-5 mandate fail-fast at runtime for this disagreement.\n const childInstRes = world.get(childRes.value, sceneInstanceToken);\n if (!childInstRes.ok) return childInstRes;\n const childMapping = (childInstRes.value as unknown as { mapping: Uint32Array }).mapping;\n mountInstances.push({ mount, root: childRes.value, mapping: childMapping });\n if (childMapping.length !== mount.memberCount) {\n return err({\n code: 'pack-mount-count-mismatch' as PackErrorCode,\n expected: 'mount.memberCount === child SceneAsset totalSlots',\n hint: PACK_ERROR_HINTS['pack-mount-count-mismatch'],\n detail: {\n code: 'pack-mount-count-mismatch',\n mountLocalId: mountLid,\n declared: mount.memberCount,\n actual: childMapping.length,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n\n // Pull the child's mapping into our parent window. Default unset slots\n // to ENTITY_NULL_RAW so downstream \"live\" checks distinguish them from\n // the first Entity (gen=0+idx=0 encodes to raw u32 0).\n const window = mount.memberCount;\n for (let k = 0; k < window; k += 1) {\n mapping[(mount.memberFirst as unknown as number) + k] = childMapping[k] ?? ENTITY_NULL_RAW;\n }\n\n // Apply mount.overrides at instantiate-time (AC-19).\n // Each override.localId addresses a slot in *this* (parent) namespace\n // (R2/F-8 cement: parent-namespace + memberFirst+offset addressing).\n // The state map will be populated below with these overrides — but we\n // must also write the value through to the live entity column so the\n // readback invariant holds.\n // Mount-entity itself never has children attached by the caller other\n // than via the recursive child; nothing else to wire here.\n if (childOfToken !== undefined) {\n if (mount.parent !== undefined) {\n // Reparent the mount-entity ChildOf to the caller-specified parent.\n const parentSlot = mount.parent as unknown as number;\n const parentEntity = mapping[parentSlot];\n if (parentEntity !== undefined && parentEntity !== ENTITY_NULL_RAW) {\n const r = world.addComponent(mountEntity, {\n component: childOfToken,\n data: { parent: parentEntity } as never,\n });\n if (!r.ok) {\n // ChildOf may already be present from layer-1; reparent via set.\n const set = world.set(mountEntity, childOfToken, {\n parent: parentEntity,\n } as never);\n if (!set.ok) return set as Result<SceneMembersSpawn, EcsError>;\n }\n } else {\n // D-8: the owned parent slot is not spawned yet (owned entities\n // spawn in step 2, after this mount loop). Defer the ChildOf wire\n // to step 2's tail once mapping[parentSlot] is live.\n mountEntitiesNeedingDeferredParent.push([mountEntity, parentSlot]);\n }\n } else {\n // R2/B-1: default semantic — mount.parent === undefined wires the\n // mount entity ChildOf to *this* scene's synthetic root (created\n // in step 3 below). Defer the actual wire to step 5 after the\n // synthetic root spawn; record the mount entity here.\n mountEntitiesNeedingRootParent.push(mountEntity);\n }\n }\n }\n\n // 2. Spawn entities[] entities. Topo-sort by ChildOf so parents are\n // spawned before children (so localId remap can read mapping live).\n // This runs AFTER mount processing (step 1) so cross-boundary\n // `ChildOf {parent: <mount-window-localId>}` references resolve\n // correctly (AC-24).\n const order = sceneTopoSort(ownEntities);\n for (const idx of order) {\n const node = ownEntities[idx];\n if (node === undefined) continue;\n const lid = node.localId as unknown as number;\n const compDataRes = worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics);\n if (!compDataRes.ok) return compDataRes;\n const sp = (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(\n ...compDataRes.value,\n );\n if (!sp.ok) return sp as Result<SceneMembersSpawn, EcsError>;\n const e = sp.value;\n mapping[lid] = e as unknown as number;\n entityToLocalId.set(e, lid as unknown as LocalEntityId);\n if (node.components.ChildOf === undefined) {\n rootEntities.push(e);\n }\n }\n\n // 2b. D-8 (feat-20260707): wire deferred owned-parent mount ChildOf edges.\n // Owned entities are now live (step 2 above), so mapping[parentSlot]\n // resolves. Same shape as the mountEntitiesNeedingRootParent wiring in\n // step 5. The relationship mirror hook (relationshipOnInsert) pushes the\n // carrier into the owned parent's Children mirror automatically.\n if (childOfToken !== undefined) {\n for (const [mountEntity, parentSlot] of mountEntitiesNeedingDeferredParent) {\n const parentEntity = mapping[parentSlot];\n if (parentEntity === undefined || parentEntity === ENTITY_NULL_RAW) continue;\n const set = world.set(mountEntity, childOfToken, { parent: parentEntity } as never);\n if (!set.ok) {\n const r = world.addComponent(mountEntity, {\n component: childOfToken,\n data: { parent: parentEntity } as never,\n });\n if (!r.ok) return r as Result<SceneMembersSpawn, EcsError>;\n }\n }\n }\n\n return ok({\n mapping,\n entityToLocalId,\n rootEntities,\n mountEntitiesNeedingRootParent,\n mountEntities,\n mountInstances,\n totalSlots,\n });\n}\n/**\n * @internal Spawn one SceneAsset's entities + apply mounts recursively, then\n * wrap them in a synthetic SceneInstance root (the anchor). This is the\n * runtime / Play / nested-mount finisher (charter P4: instance ==\n * entity-with-SceneInstance). Caller (`_instantiateSceneRec`) owns cycle\n * bookkeeping.\n */\nexport function worldInstantiateSceneAsset(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n parent: EntityHandle | undefined,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const childOfToken = world.components.resolve('ChildOf');\n\n const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);\n if (!membersRes.ok) return membersRes;\n const { mapping, entityToLocalId, rootEntities, mountEntitiesNeedingRootParent, totalSlots } =\n membersRes.value;\n const { mountInstances } = membersRes.value;\n const ownMounts = asset.mounts ?? [];\n\n // 3. Spawn the synthetic root entity carrying SceneInstance.\n // First alloc the state ref so the SceneInstance.state column has a\n // live u32; then attach SceneInstance to a fresh entity.\n let stateRef: Handle<'SceneInstanceState', 'unique'>;\n stateRef = world.allocUniqueRef('SceneInstanceState', null, () => {\n sceneWorldState(world).statePayloads.delete(Number(stateRef));\n });\n // Spawn the root with SceneInstance component, mapping snapshot, and\n // state ref. The mapping is a Uint32Array (array<entity> field shape).\n // Convert mapping Uint32Array to plain number[] for spawn write — the\n // ECS array<entity> arm copies element-by-element and accepts both, but\n // the plain-array form sidesteps a Uint32Array.length=0 corner case\n // observed during M2 testing where a non-empty Uint32Array was written\n // as if empty (suspect: archetype write-array dispatch on instanceof\n // Array vs TypedArray).\n const mappingPlain: number[] = Array.from(mapping);\n // The synthetic root is the ChildOf parent of every owned root entity\n // (step 5 below) and may itself become a ChildOf parent of a caller-\n // supplied `parent` chain. propagateTransforms walks ChildOf parents\n // through the Transform liveMap and treats a parent missing Transform\n // as `hierarchy-broken`, so the synthetic root must carry Transform\n // (identity TRS via layer-2 defaults) when Transform is defined.\n const rootComponents: ComponentData[] = [\n {\n component: sceneInstanceToken,\n data: {\n source: handle,\n mapping: mappingPlain,\n state: stateRef,\n } as never,\n },\n ];\n const transformToken = world.components.resolve('Transform');\n if (transformToken !== undefined) {\n rootComponents.push({\n component: transformToken,\n data: {} as never,\n });\n }\n const rootSpawn = (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(\n ...rootComponents,\n );\n if (!rootSpawn.ok) {\n return rootSpawn;\n }\n const rootEntity = rootSpawn.value;\n\n // 4. Build SceneInstanceState payload + register it in the UniqueRefStore\n // under the same handle. We use the public `_setUniqueRefPayload`\n // helper (added below) so the alloc -> populate sequence stays atomic.\n const overrides = new Map<LocalEntityId, Map<string, MountOverride>>();\n for (const mount of ownMounts) {\n for (const ov of mount.overrides ?? []) {\n // feat-20260713 M2 / w8: `MountOverride.field` is optional (add-or-patch\n // discriminant carried by the shape itself). Record the override into\n // the SceneInstanceState map keyed by comp (no field) or comp:field\n // (field-patch), then apply it to the live member column via the shared\n // add-or-patch helper.\n const lid = ov.localId as unknown as LocalEntityId;\n let fieldMap = overrides.get(lid);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n overrides.set(lid, fieldMap);\n }\n fieldMap.set(mountOverrideStateKey(ov), ov);\n // Apply override to the live member entity column.\n const memberEntityRaw = mapping[lid as unknown as number];\n if (memberEntityRaw !== undefined && memberEntityRaw !== ENTITY_NULL_RAW) {\n const memberEntity = memberEntityRaw as unknown as EntityHandle;\n const applyRes = worldApplyMountOverride(world, memberEntity, ov);\n if (!applyRes.ok) {\n return applyRes as Result<EntityHandle, EcsError>;\n }\n }\n }\n }\n\n const detached = new Set<LocalEntityId>();\n const state: Record<string, unknown> = {\n source: handle,\n entityToLocalId,\n detachedLocalIds: detached,\n // Convert overrides Map<LocalEntityId, Map<string, MountOverride>>\n // into Map<LocalEntityId, Map<string, SceneInstanceOverrideRecord>>\n overrides: worldMountOverridesToStateMap(overrides),\n rootEntities,\n mountRoots: mountInstances.map(({ root }) => root),\n totalSlots,\n mountTimeOverrides: ownMounts.flatMap((m) => m.overrides ?? []),\n };\n // Stuff the state into the UniqueRefStore under the existing slot. We\n // re-use the slot we allocated above by writing directly into the\n // payloads map via a `_setUniqueRefPayload` shim.\n worldSetUniqueRefPayload(world, stateRef, state);\n\n // 5. Wire ChildOf for every owned root entity (no ChildOf at layer-1)\n // to the synthetic root.\n if (childOfToken !== undefined) {\n for (const rootE of rootEntities) {\n const has = world.get(rootE, childOfToken);\n if (!has.ok) {\n // No ChildOf yet — attach to synthetic root.\n const r = world.addComponent(rootE, {\n component: childOfToken,\n data: { parent: rootEntity } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n // R2/B-1: wire mount entities with default `mount.parent === undefined`\n // to this scene's synthetic root. _spawnMountEntity may have attached a\n // placeholder ChildOf {parent: ENTITY_NULL_RAW} when mount.components\n // was empty; overwrite via set so the ChildOf chain meshRenderer ->\n // childSyntheticRoot -> mountEntity -> outerSyntheticRoot resolves\n // through Transform-bearing parents (AC-16 / requirements S-7).\n for (const mountE of mountEntitiesNeedingRootParent) {\n const set = world.set(mountE, childOfToken, { parent: rootEntity } as never);\n if (!set.ok) {\n const r = world.addComponent(mountE, {\n component: childOfToken,\n data: { parent: rootEntity } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n // Caller-supplied parent: synthetic root's ChildOf -> parent.\n if (parent !== undefined) {\n const r = world.addComponent(rootEntity, {\n component: childOfToken,\n data: { parent } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n\n return ok(rootEntity);\n}\n/**\n * @internal Flat finisher — spawn one SceneAsset's members WITHOUT wrapping\n * them in a synthetic SceneInstance root and WITHOUT forcing `ChildOf` onto\n * top-level members. Used for \"opening a scene to edit\": the scene's own\n * entities become plain top-level world entities whose hierarchy is exactly\n * their authored `ChildOf`. Nested prefabs inside still materialise as their\n * own SceneInstance anchors (the mount recursion in `_spawnSceneMembers` is\n * always anchored). Returns the top-level handles (own rootless entities +\n * top-level mount carriers).\n */\nexport function worldInstantiateSceneAssetFlat(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<{ roots: EntityHandle[]; mountEntities: EntityHandle[] }, EcsError> {\n const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);\n if (!membersRes.ok) return membersRes;\n const { rootEntities, mountEntitiesNeedingRootParent, mountEntities, mountInstances } =\n membersRes.value;\n const childOfToken = world.components.resolve('ChildOf');\n\n // Apply parent mount overrides to the live columns and record them on the\n // nested child anchor. Flat mode has no outer SceneInstance state; without\n // this hand-authored mounts[].overrides affect the live value but disappear\n // from the child state, so Gateway re-open cannot discover or revert them.\n for (const { mount, root, mapping: childMapping } of mountInstances) {\n const childStateRes = worldGetSceneInstanceState(world, root);\n if (!childStateRes.ok) return childStateRes;\n for (const ov of mount.overrides ?? []) {\n const childLocalId =\n (ov.localId as unknown as number) - (mount.memberFirst as unknown as number);\n const memberEntityRaw = childMapping[childLocalId];\n if (memberEntityRaw === undefined || memberEntityRaw === ENTITY_NULL_RAW) continue;\n const memberEntity = memberEntityRaw as unknown as EntityHandle;\n const applyRes = worldApplyMountOverride(world, memberEntity, ov);\n if (!applyRes.ok) {\n return applyRes as Result<\n { roots: EntityHandle[]; mountEntities: EntityHandle[] },\n EcsError\n >;\n }\n let fieldMap = childStateRes.value.overrides.get(childLocalId as LocalEntityId);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n childStateRes.value.overrides.set(childLocalId as LocalEntityId, fieldMap);\n }\n fieldMap.set(mountOverrideStateKey(ov), {\n comp: ov.comp,\n ...(ov.field === undefined ? {} : { field: ov.field }),\n value: ov.value,\n });\n }\n }\n\n // Default-parented mount carriers (`mount.parent === undefined`) would, in\n // anchor mode, attach to the synthetic root. Flat mode has none, so they\n // stay top-level. `_spawnMountEntity` may have left a placeholder\n // `ChildOf {parent: ENTITY_NULL_RAW}` (rare: mount with no components AND\n // Transform unregistered) — strip it so the carrier is a genuine root.\n if (childOfToken !== undefined) {\n for (const mountE of mountEntitiesNeedingRootParent) {\n const co = world.get(mountE, childOfToken);\n if (co.ok && (co.value as { parent: number }).parent === ENTITY_NULL_RAW) {\n world.removeComponent(mountE, childOfToken);\n }\n }\n }\n\n return ok({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });\n}\n/** @internal Build ComponentData[] for one SceneEntity, remapping localIds.\n *\n * C-R2 (feat-20260622-s5 M6): unknown fields on a SceneAsset payload are NOT\n * fatal. Unlike `world.spawn` (an explicit API call where a typo is a\n * programming error -> `SpawnDataUnknownFieldError`), scene data is loader-fed\n * and may carry a stale / deprecated / typo'd field. The remap below builds a\n * fresh `remappedRaw` and simply SKIPS keys absent from the schema (no input\n * mutation — the source `raw` is never deleted-from), recording each skipped\n * key as a non-fatal `SceneInstantiateDiagnostic` into the passed accumulator.\n * All known fields still write through, so one bad field cannot blank the\n * entity or the scene (C-AC-02/03/04).\n */\nexport function worldBuildSceneEntityComponentDatas(\n world: World,\n node: import('@forgeax/engine-types').SceneEntity,\n mapping: Uint32Array,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<ComponentData[], EcsError> {\n const out: ComponentData[] = [];\n const nodeLocalId = node.localId as unknown as number;\n for (const compName of Object.keys(node.components)) {\n const token = world.components.resolve(compName);\n if (token === undefined) {\n return err(new ComponentNotDefinedError(compName));\n }\n const raw = node.components[compName] ?? {};\n const schema = componentSchema(token) as Record<string, string>;\n const remappedRaw: Record<string, unknown> = {};\n for (const fieldName of Object.keys(raw)) {\n const fieldType = schema[fieldName];\n // C-R2: unknown key -> skip (do not copy into remappedRaw, do not\n // mutate the source `raw`) and record a structured diagnostic. The\n // downstream `spawn` only sees schema-valid keys, so its own\n // validateComponentDataKeys gate stays green.\n if (fieldType === undefined) {\n diagnostics.push({ component: compName, field: fieldName, localId: nodeLocalId });\n continue;\n }\n const value = (raw as Record<string, unknown>)[fieldName];\n const kind = classifyEntityField(token, fieldName);\n if (kind !== null) {\n // Entity / array<entity> field — remap through the shared kernel.\n // localId -> live Entity. Slots not yet spawned hold ENTITY_NULL_RAW.\n const sceneRemap = (localId: number): number => {\n if (localId < 0 || localId >= mapping.length) return ENTITY_NULL_RAW;\n const live = mapping[localId];\n return live === undefined || live === ENTITY_NULL_RAW ? ENTITY_NULL_RAW : live;\n };\n remappedRaw[fieldName] = remapEntityFieldValue(value, kind, sceneRemap);\n } else {\n remappedRaw[fieldName] = value;\n }\n }\n const filled = fillComponentDefaults(token, remappedRaw);\n out.push({ component: token, data: filled as never });\n }\n return ok(out);\n}\n/**\n * @internal feat-20260713 M2 / w8: apply one MountOverride to a live member\n * entity column. The `field?` shape is the add-or-patch discriminant:\n *\n * - `field` present -> PATCH one field: `world.set(member, comp, {[field]:\n * value})`. Omitted fields keep their authored / existing values.\n * - `field` absent -> ADD/UPSERT the whole component: `value` is the\n * per-field value map for `comp`. When the member already carries `comp`\n * it is upserted (set-over each supplied field + schema defaults for the\n * omitted ones — the whole component is rewritten from the value map +\n * defaults, never a `component-already-present` error). When absent it is\n * added fresh via `addComponent` (fillComponentDefaults fills omitted\n * fields). The value-map is fed through `fillComponentDefaults` so the\n * add and upsert paths write byte-identical rows.\n *\n * Component registration + value-key validation happened at\n * `_validateMountOverrides` (fail-fast before any spawn); by this point the\n * comp resolves through the World-local catalog and the value keys are schema-valid.\n * still guards defensively (an unregistered comp is a no-op skip, matching\n * the prior field-patch behaviour). Returns the underlying set / addComponent\n * Result so a shared-field value gate (D-4) or any other write error\n * propagates unchanged.\n */\nexport function worldApplyMountOverride(\n world: World,\n member: EntityHandle,\n ov: MountOverride,\n): Result<void, EcsError> {\n const ovToken = world.components.resolve(ov.comp);\n if (ovToken === undefined) return ok(undefined);\n if (ov.field !== undefined) {\n // PATCH one field.\n return world.set(member, ovToken, { [ov.field]: ov.value } as never);\n }\n // ADD/UPSERT the whole component. Fill omitted fields from the schema so\n // add and upsert produce identical rows (upsert = full rewrite from the\n // value map + defaults).\n const rawValue = (ov.value ?? {}) as Record<string, unknown>;\n const filled = fillComponentDefaults(ovToken as Component, rawValue);\n const has = world.get(member, ovToken);\n if (has.ok) {\n // Already present -> upsert (set every filled field, no duplicate error).\n return world.set(member, ovToken, filled as never);\n }\n return world.addComponent(member, { component: ovToken, data: filled as never });\n}\n/**\n * @internal R2/B-3 + R2/B-4: validate `mount.overrides[]` BEFORE any\n * spawn so a malformed override fails fast with no observable side\n * effects (charter P3 explicit-failure). Two checks:\n *\n * 1. `override.localId` must address a slot inside the parent-namespace\n * member window `[memberFirst, memberFirst + memberCount)` (AC-06).\n * 2. `override.field` must exist in the resolved component schema\n * (AC-07). When the component is unregistered we cannot validate the\n * field shape; let the existing fall-through path proceed (the\n * catalog guard inside the override-application loop\n * will skip the write).\n */\nexport function worldValidateMountOverrides(\n world: World,\n mount: SceneInstanceMount,\n): Result<void, EcsError> {\n const overrides = mount.overrides;\n if (overrides === undefined) return ok(undefined);\n const memberFirst = mount.memberFirst as unknown as number;\n const memberCount = mount.memberCount;\n const memberLast = memberFirst + memberCount;\n const mountLid = mount.localId as unknown as number;\n for (const ov of overrides) {\n const ovLid = ov.localId as unknown as number;\n // R2/B-3: parent-namespace check — override.localId must lie in the\n // member window [memberFirst, memberFirst + memberCount).\n if (ovLid < memberFirst || ovLid >= memberLast) {\n return err({\n code: 'pack-mount-override-localid-out-of-range' as PackErrorCode,\n expected: `override.localId in [${memberFirst}, ${memberLast})`,\n hint: PACK_ERROR_HINTS['pack-mount-override-localid-out-of-range'],\n detail: {\n code: 'pack-mount-override-localid-out-of-range',\n overrideLocalId: ovLid,\n mountLocalId: mountLid,\n memberCount,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n // feat-20260713 M2 / w8: double-branch schema check.\n // - field-patch form (field present): the component (when registered)\n // must declare `override.field` in its schema (R2/B-4, unchanged).\n // - component-add form (field absent): the component MUST be registered\n // (component-not-defined otherwise) AND every key in the value map\n // must be a schema field (pack-mount-override-unknown-field).\n const ovToken = world.components.resolve(ov.comp);\n if (ov.field !== undefined) {\n if (ovToken !== undefined) {\n const schema = componentSchema(ovToken) as Record<string, unknown>;\n if (!(ov.field in schema)) {\n return err({\n code: 'pack-mount-override-unknown-field' as PackErrorCode,\n expected: `override.field defined on component '${ov.comp}'`,\n hint: PACK_ERROR_HINTS['pack-mount-override-unknown-field'],\n detail: {\n code: 'pack-mount-override-unknown-field',\n comp: ov.comp,\n field: ov.field,\n mountLocalId: mountLid,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n } else {\n // component-add form: comp must be registered so we can validate + apply\n // the whole component (add/upsert needs the schema).\n if (ovToken === undefined) {\n return err(new ComponentNotDefinedError(ov.comp));\n }\n const schema = componentSchema(ovToken) as Record<string, unknown>;\n const valueMap = (ov.value ?? {}) as Record<string, unknown>;\n for (const key of Object.keys(valueMap)) {\n if (!(key in schema)) {\n return err({\n code: 'pack-mount-override-unknown-field' as PackErrorCode,\n expected: `override.value keys defined on component '${ov.comp}'`,\n hint: PACK_ERROR_HINTS['pack-mount-override-unknown-field'],\n detail: {\n code: 'pack-mount-override-unknown-field',\n comp: ov.comp,\n field: key,\n mountLocalId: mountLid,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n }\n }\n return ok(undefined);\n}\n/** @internal Spawn the mount-entity slot carrying mount.components (if any).\n *\n * R2/B-1: the mount entity is a structural intermediate in the ChildOf\n * chain `cube -> innerSyntheticRoot -> mountEntity -> outerSyntheticRoot`,\n * so it MUST carry Transform whenever Transform is registered (mirrors\n * the D-V-0 synthetic-root invariant). Otherwise propagateTransforms\n * walking the chain hits a Transform-less parent and emits per-frame\n * `RhiError(hierarchy-broken)` (verify R1 root cause of the\n * hello-scene-nesting demo black frames).\n */\nexport function worldSpawnMountEntity(\n world: World,\n mount: SceneInstanceMount,\n mapping: Uint32Array,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const fakeNode: import('@forgeax/engine-types').SceneEntity = {\n localId: mount.localId,\n components: mount.components ?? {},\n };\n const cdRes = worldBuildSceneEntityComponentDatas(world, fakeNode, mapping, diagnostics);\n if (!cdRes.ok) return cdRes;\n // R2/B-1: ensure Transform is attached so propagateTransforms can walk\n // through this entity. Layer-2 defaults supply identity TRS; the\n // mount.components overlay (when present and including Transform) takes\n // precedence and is already in cdRes.value.\n const transformToken = world.components.resolve('Transform');\n if (transformToken !== undefined) {\n const hasTransform = cdRes.value.some((c) => c.component === transformToken);\n if (!hasTransform) {\n cdRes.value.push({ component: transformToken, data: {} as never });\n }\n }\n if (cdRes.value.length === 0) {\n // Mount has no components AND Transform is unregistered (rare unit-\n // test path). Fall back to the placeholder ChildOf so the spawn has\n // a real archetype. Step 5 overwrites this placeholder.\n const childOfToken = world.components.resolve('ChildOf');\n if (childOfToken === undefined) {\n return err(new ComponentNotDefinedError('ChildOf'));\n }\n cdRes.value.push({\n component: childOfToken,\n data: { parent: ENTITY_NULL_RAW } as never,\n });\n }\n return (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(...cdRes.value);\n}\n/** @internal Resolve mount.source through the wired SceneAssetResolver. */\nexport function worldResolveMountSource(\n world: World,\n source: number | string,\n parentHandle: Handle<'SceneAsset', 'shared'>,\n): Result<Handle<'SceneAsset', 'shared'>, EcsError> {\n const resolver = worldGetSceneAssetResolver(world);\n if (resolver === null) {\n return err({\n code: 'stale-entity' as const,\n expected: 'wired SceneAssetResolver (auto-wired by engine.assets.instantiate)',\n hint:\n 'engine.assets.instantiate sugar wires this for you; ' +\n 'call worldSetSceneAssetResolver before nested scene expansion.',\n detail: { entity: 0, slot: 0, generation: 0 },\n } as unknown as EcsError);\n }\n const r = resolver(source, parentHandle);\n if (!r.ok) {\n // Resolver carries `unknown` err (loose contract — engine-runtime may\n // wire any shape); narrow back to EcsError here at the boundary.\n return err(r.error as EcsError);\n }\n return ok(r.value);\n}\n/** @internal Convert mount.overrides Map shape to the SceneInstanceState shape.\n *\n * feat-20260713 M1 / w4: `field` is optional (add-or-patch discriminant). In\n * M1 only the field-patch form reaches this builder (the component-add form\n * fails fast in the apply loops); the record type stays `field?: string` so\n * the M2 add path can flow through untouched. `exactOptionalPropertyTypes`\n * forbids writing an explicit `field: undefined`, so omit the key when absent.\n */\nexport function worldMountOverridesToStateMap(\n src: Map<LocalEntityId, Map<string, MountOverride>>,\n): Map<LocalEntityId, Map<string, { comp: string; field?: string; value: unknown }>> {\n const out = new Map<\n LocalEntityId,\n Map<string, { comp: string; field?: string; value: unknown }>\n >();\n for (const [lid, fields] of src) {\n const m = new Map<string, { comp: string; field?: string; value: unknown }>();\n for (const [k, v] of fields) {\n m.set(k, {\n comp: v.comp,\n value: v.value,\n ...(v.field !== undefined ? { field: v.field } : {}),\n });\n }\n out.set(lid, m);\n }\n return out;\n}\n/** @internal Set the payload of an already-allocated SceneInstance state ref. */\nexport function worldSetUniqueRefPayload<T>(\n world: World,\n handle: Handle<string, 'unique'>,\n payload: T,\n): void {\n sceneWorldState(world).statePayloads.set(Number(handle), payload);\n}\n\n/**\n * @internal Resolve the SceneInstanceState payload behind the\n * `SceneInstance.state` ref column on `root`. Returns Err when `root`\n * does not carry SceneInstance or the ref slot is dead.\n */\nexport function worldResolveSceneInstanceStatePayload(\n world: World,\n root: EntityHandle,\n): Result<SceneInstanceStatePayload, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const r = world.get(root, sceneInstanceToken);\n if (!r.ok) return r;\n const stateRefRaw = (r.value as unknown as { state: number }).state;\n const stateRefHandle = toUnique<'SceneInstanceState'>(stateRefRaw);\n const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));\n if (payload === undefined) {\n return err(\n new StaleEntityError(root as unknown as number, entityIndex(root), entityGeneration(root), {\n operation: 'resolveSceneInstanceState',\n component: 'SceneInstance',\n expectedGeneration: entityGeneration(root),\n actualGeneration: entityGeneration(root),\n }),\n );\n }\n return ok(payload as SceneInstanceStatePayload);\n}\n/**\n * Public sugar — get the SceneInstanceState payload (Map / Set view) for\n * `root`. Equivalent to `world.get(root, SceneInstance)` followed by a\n * managed-ref resolution; provided so AI users do not have to learn the\n * `ref<T>` slot resolution mechanic for the common read path.\n */\nexport function worldGetSceneInstanceState(\n world: World,\n root: EntityHandle,\n): Result<SceneInstanceStatePayload, EcsError> {\n return worldResolveSceneInstanceStatePayload(world, root);\n}\n/**\n * Despawn a SceneInstance root + all its members. `opts.keepDetached`\n * preserves members marked via `worldDetachSceneMember` (plan-strategy\n * §D-5). Returns the count of entities actually despawned (root + each\n * non-detached member).\n *\n * For a plain entity (no SceneInstance), behaviour matches\n * `world.despawn(entity)` followed by `despawnDescendants(entity)` — i.e.\n * `keepDetached` is a no-op.\n */\nexport function worldDespawnScene(\n world: World,\n root: EntityHandle,\n opts?: { keepDetached?: boolean },\n): Result<number, EcsError> {\n const dRes = worldDespawnDescendants(world, root, opts);\n if (!dRes.ok) return dRes;\n const drop = world.despawn(root);\n if (!drop.ok) return drop;\n return ok(dRes.value + 1);\n}\n/**\n * Despawn every descendant of `root` reachable through Children mirror /\n * SceneInstance.mapping. `opts.keepDetached` is honoured only when `root`\n * carries a SceneInstance (otherwise the option is ignored — there is no\n * detached set on a plain entity).\n *\n * Returns the count of entities despawned. The `root` itself is NOT\n * despawned (that is `despawnScene`'s extra step).\n */\nexport function worldDespawnDescendants(\n world: World,\n root: EntityHandle,\n opts?: { keepDetached?: boolean },\n): Result<number, EcsError> {\n let detached: Set<LocalEntityId> | null = null;\n let entityToLocalId: Map<EntityHandle, LocalEntityId> | null = null;\n if (opts?.keepDetached === true) {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (stateRes.ok) {\n detached = stateRes.value.detachedLocalIds;\n entityToLocalId = stateRes.value.entityToLocalId;\n }\n }\n let count = 0;\n // Collect descendants first (DFS via iterDescendants) to avoid mutating\n // while iterating. SceneInstance.mapping also owns members that may not be\n // reachable through a Children mirror in a partially registered host. The\n // nested anchor list closes that same ownership boundary for mounted scenes.\n const list: EntityHandle[] = [];\n const seen = new Set<number>();\n const collect = (anchor: EntityHandle): void => {\n for (const e of world.iterDescendants(anchor)) {\n const raw = e as unknown as number;\n if (!seen.has(raw)) {\n seen.add(raw);\n list.push(e);\n }\n }\n const stateRes = worldResolveSceneInstanceStatePayload(world, anchor);\n if (!stateRes.ok) return;\n for (const e of stateRes.value.entityToLocalId.keys()) {\n const raw = e as unknown as number;\n if (!seen.has(raw)) {\n seen.add(raw);\n list.push(e);\n }\n }\n for (const nestedRoot of stateRes.value.mountRoots) {\n const raw = nestedRoot as unknown as number;\n if (seen.has(raw)) continue;\n seen.add(raw);\n list.push(nestedRoot);\n collect(nestedRoot);\n }\n };\n collect(root);\n const childOfToken = world.components.resolve('ChildOf');\n // ChildOf uses linkedSpawn, so a parent-first pass would recursively retire\n // its children before this function can count them. Sort the ownership set\n // by its live ChildOf depth instead of relying on mirror traversal order;\n // nested SceneInstance roots are siblings of their mount carrier in the\n // flattened traversal but parents of the mounted members.\n const owned = new Set(list.map((entity) => Number(entity)));\n const ownedDepth = (entity: EntityHandle): number => {\n if (childOfToken === undefined) return 0;\n let current = entity;\n let depth = 0;\n const visited = new Set<number>();\n while (!visited.has(Number(current))) {\n visited.add(Number(current));\n const parentRes = world.get(current, childOfToken);\n if (!parentRes.ok) break;\n const parent = (parentRes.value as { parent: EntityHandle }).parent;\n if (!owned.has(Number(parent))) break;\n depth += 1;\n current = parent;\n }\n return depth;\n };\n list.sort((a, b) => ownedDepth(b) - ownedDepth(a));\n for (const e of list) {\n if (detached !== null) {\n const lid = entityToLocalId?.get(e);\n if (lid !== undefined && detached.has(lid)) {\n if (childOfToken !== undefined) {\n world.removeComponent(e, childOfToken);\n }\n continue;\n }\n }\n const r = world.despawn(e);\n if (!r.ok) {\n if (r.error.code === 'stale-entity') continue;\n return r;\n }\n count += 1;\n }\n return ok(count);\n}\n/**\n * Write a runtime override to a member entity belonging to `root`. Routes\n * through `world.set(member, comp, { [field]: value })` after an entity-\n * scope guard so cross-instance writes fail-fast. Type-mismatch surfaces\n * `EcsErrorCode = 'scene-override-type-mismatch'` (D-9).\n */\nexport function worldSetSceneOverride<S extends ComponentSchema>(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n component: Component<string, S>,\n field: keyof ShapeOf<S> & string,\n value: unknown,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) {\n return err(\n new StaleEntityError(\n member as unknown as number,\n entityIndex(member),\n entityGeneration(member),\n {\n operation: 'setSceneOverride',\n component: component.name,\n expectedGeneration: entityGeneration(member),\n actualGeneration: entityGeneration(member),\n },\n ),\n );\n }\n // Type guard: only check primitive scalar field types where we can\n // narrow `typeof`; ref / handle / entity / array / buffer fields skip\n // (write would surface a deeper error from set).\n const schemaType = (componentSchema(component) as Record<string, string>)[field];\n if (schemaType !== undefined && isPrimitiveScalarFieldType(schemaType)) {\n const expectJsType = primitiveJsType(schemaType);\n const actualJsType = typeof value;\n if (expectJsType !== actualJsType) {\n return err({\n code: 'scene-override-type-mismatch' as const,\n expected: `value typeof === ${expectJsType}`,\n hint:\n `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, ` +\n `got ${actualJsType}; coerce or pick a different override path.`,\n detail: {\n code: 'scene-override-type-mismatch' as const,\n comp: component.name,\n field: field as string,\n expectedType: schemaType,\n actualType: actualJsType,\n },\n } as unknown as EcsError);\n }\n }\n const setRes = world.set(member, component, { [field]: value } as Partial<InputShapeOf<S>>);\n if (!setRes.ok) return setRes;\n // Record into state.overrides\n let fieldMap = state.overrides.get(lid);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n state.overrides.set(lid, fieldMap);\n }\n fieldMap.set(`${component.name}:${field}`, {\n comp: component.name,\n field: field as string,\n value,\n });\n return ok(undefined);\n}\n/**\n * Drop a runtime override (and any mount-time override for the same\n * (member, comp, field) triple); roll the live column value back to the\n * source SceneAsset's layer-1 explicit value (M2 v1 — M3+ widens to layer\n * 2/3 defaults via fillComponentDefaults).\n */\nexport function worldRemoveSceneOverride<S extends ComponentSchema>(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n component: Component<string, S>,\n field: keyof ShapeOf<S> & string,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n const fieldMap = state.overrides.get(lid);\n if (fieldMap !== undefined) {\n fieldMap.delete(`${component.name}:${field}`);\n if (fieldMap.size === 0) state.overrides.delete(lid);\n }\n // Look up the source SceneAsset layer-1 value.\n const assetRes = worldResolveSceneAsset(world, state.source);\n if (!assetRes.ok) return assetRes;\n const node = assetRes.value.entities.find(\n (n) => (n.localId as unknown as number) === (lid as unknown as number),\n );\n const layer1 = node?.components[component.name] as Record<string, unknown> | undefined;\n if (layer1 !== undefined && field in layer1) {\n const r = world.set(member, component, { [field]: layer1[field] } as Partial<InputShapeOf<S>>);\n if (!r.ok) return r;\n }\n return ok(undefined);\n}\n/** Mark a member entity detached. Idempotent (set semantics). */\nexport function worldDetachSceneMember(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n): Result<void, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n state.detachedLocalIds.add(lid);\n return ok(undefined);\n}\n/** Clear a detached mark. Idempotent (set semantics). */\nexport function worldReattachSceneMember(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n state.detachedLocalIds.delete(lid);\n return ok(undefined);\n}\n/**\n * Get the SceneAsset handle a SceneInstance root was instantiated from.\n * Returns Err on a plain entity (no SceneInstance component).\n */\nexport function worldGetSceneAssetForInstance(\n world: World,\n root: EntityHandle,\n): Result<Handle<'SceneAsset', 'shared'>, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n return ok(stateRes.value.source);\n}\n\n// SceneInstanceStatePayload — internal echo of the runtime\n// `SceneInstanceState` interface for ECS-side consumption (engine-ecs cannot\n// value-import engine-runtime by AC-29; structural shape only).\n// ────────────────────────────────────────────────────────────────────────────\n\n/** @internal Structural payload behind `SceneInstance.state` ref column. */\nexport interface SceneInstanceStatePayload {\n readonly source: Handle<'SceneAsset', 'shared'>;\n readonly entityToLocalId: Map<EntityHandle, LocalEntityId>;\n readonly detachedLocalIds: Set<LocalEntityId>;\n readonly overrides: Map<\n LocalEntityId,\n Map<string, { readonly comp: string; readonly field?: string; readonly value: unknown }>\n >;\n readonly rootEntities: EntityHandle[];\n /** Synthetic roots of recursively mounted SceneAssets owned by this instance. */\n readonly mountRoots: EntityHandle[];\n readonly totalSlots: number;\n readonly mountTimeOverrides: readonly MountOverride[];\n}\n\n/**\n * Topological sort over the implicit ChildOf graph (parents before children).\n * Cycle-free input always covers all n nodes; cyclic input emits whatever was\n * reachable from indegree-0 (the fallback caller handles cycle reporting via\n * `pack-cyclic-reference` at the upstream scanner / runtime path).\n */\nfunction sceneTopoSort(\n nodes: readonly import('@forgeax/engine-types').SceneEntity[],\n): readonly number[] {\n const n = nodes.length;\n const childrenOf: number[][] = Array.from({ length: n }, () => []);\n const indeg = new Uint32Array(n);\n const localIdToIdx = new Map<number, number>();\n for (let i = 0; i < n; i += 1) {\n const node = nodes[i];\n if (node === undefined) continue;\n localIdToIdx.set(node.localId as unknown as number, i);\n }\n for (let i = 0; i < n; i += 1) {\n const node = nodes[i];\n if (node === undefined) continue;\n const child = node.components.ChildOf;\n if (child === undefined) continue;\n const p = (child as Record<string, unknown>).parent;\n if (typeof p === 'number') {\n const parentIdx = localIdToIdx.get(p);\n if (parentIdx !== undefined && parentIdx !== i) {\n childrenOf[parentIdx]?.push(i);\n indeg[i] = (indeg[i] ?? 0) + 1;\n }\n }\n }\n const order: number[] = [];\n const queue: number[] = [];\n for (let i = 0; i < n; i += 1) if ((indeg[i] ?? 0) === 0) queue.push(i);\n while (queue.length > 0) {\n const head = queue.shift();\n if (head === undefined) break;\n order.push(head);\n for (const c of childrenOf[head] ?? []) {\n indeg[c] = (indeg[c] ?? 0) - 1;\n if ((indeg[c] ?? 0) === 0) queue.push(c);\n }\n }\n // Append any nodes left unvisited (defensive — cycle would surface here).\n for (let i = 0; i < n; i += 1) {\n if (!order.includes(i) && nodes[i] !== undefined) order.push(i);\n }\n return order;\n}\n\n/**\n * @internal feat-20260713 M2 / w8: SceneInstanceState map key for a\n * MountOverride. Field-patch form keys by `comp:field` (one entry per patched\n * field); component-add form keys by `comp` (one entry per added component). The\n * two key shapes cannot collide because a field-patch always carries a `:field`\n * suffix. Later array entries for the same key overwrite earlier ones, matching\n * the array-order apply semantics.\n */\nfunction mountOverrideStateKey(ov: MountOverride): string {\n return ov.field !== undefined ? `${ov.comp}:${ov.field}` : ov.comp;\n}\n\n/** Schema field types that are JS primitives (typeof checkable). */\nfunction isPrimitiveScalarFieldType(fieldType: string): boolean {\n if (\n fieldType === 'f32' ||\n fieldType === 'f64' ||\n fieldType === 'u32' ||\n fieldType === 'i32' ||\n fieldType === 'u8' ||\n fieldType === 'i8' ||\n fieldType === 'u16' ||\n fieldType === 'i16' ||\n fieldType === 'bool' ||\n fieldType === 'string'\n ) {\n return true;\n }\n if (fieldType.startsWith('enum<')) return true;\n return false;\n}\n\n/** Map a primitive scalar field type to the runtime `typeof` it should narrow to. */\nfunction primitiveJsType(fieldType: string): string {\n if (fieldType === 'bool') return 'boolean';\n if (fieldType === 'string') return 'string';\n return 'number';\n}\n","import type { EntityHandle } from '@forgeax/engine-ecs';\n\nexport type SceneErrorCode = 'hierarchy-broken' | 'hierarchy-cycle';\n\n/** Scene-instantiation failures owned by the scene package. */\nexport type SceneInstanceErrorCode = 'component-not-defined' | 'scene-override-type-mismatch';\n\nexport { ComponentNotDefinedError } from '@forgeax/engine-ecs/projection';\n\nexport interface SceneErrorDetail {\n readonly entity: EntityHandle;\n readonly parent: EntityHandle;\n}\n\nexport class SceneError extends Error {\n readonly code: SceneErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: SceneErrorDetail | undefined;\n\n constructor(args: {\n code: SceneErrorCode;\n expected: string;\n hint: string;\n detail?: SceneErrorDetail;\n }) {\n super(`[SceneError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'SceneError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n","// @forgeax/engine-runtime - Children (forward-list of child entities).\n//\n// Schema: 1 array<entity> field `entities` (variable-length, ECS-managed via\n// the BufferPool slot column + sidecar count column allocated by the ECS M2\n// `world.push` / `world.pop` / `world.capacity` command surface).\n//\n// feat-20260515-buffer-array-vocab-collapse M3 / w17:\n// the legacy `VarArrayView<Entity>` value-shape wrapper was retired in\n// favour of a direct `TypedArray` snapshot returned by `world.get` plus the\n// three `world` commands. AI users mutate the list through:\n//\n// world.push(parent, Children, 'entities', child).unwrap();\n// world.pop(parent, Children, 'entities').unwrap();\n// world.capacity(parent, Children, 'entities').unwrap();\n//\n// And read through the read-only `Uint32Array` snapshot:\n//\n// const snap = world.get(parent, Children).unwrap().entities;\n// const liveCount = snap.length;\n// for (let i = 0; i < liveCount; i++) { const child = snap[i]; ... }\n//\n// Snapshot length equals the live element count (sidecar count column owned\n// by the ECS layer); the snapshot is rematerialised on every `world.get`\n// (D-4 no-cache); writes routed through the snapshot are undefined behaviour\n// (the contract is read-only, plan-strategy §2.2 D-R3).\n//\n// feat-20260531-ecs-relationship-abstraction-bidirectional-sync M4 / t20:\n// Children is the MIRROR side of the ChildOf relationship. Its schema is\n// unchanged (the `entities: 'array<entity>'` shape is exactly what the\n// relationship mirror contract requires), but the engine now maintains this\n// list automatically whenever ChildOf is added / removed / reparented on a\n// child entity (M2 bidirectional-sync hook on ChildOf). The prior OOS-10\n// \"AI users keep the two sides consistent themselves\" contract is retired:\n// `world.addComponent(child, ChildOf{parent})` appends `child` to\n// `parent.Children.entities`, `world.removeComponent` / reparent prunes it.\n// AI users still MAY push/pop the list manually (the three `world` commands\n// below remain valid for non-ChildOf forward-lists), but for the ChildOf\n// hierarchy the engine owns consistency.\n//\n// feat-20260514-ecs-children-instances-managed-buffer-array M3 / w13 (kept\n// for context): migrated from the legacy `{ count: 'u32' }` advisory marker\n// to the real variable-length entity-array storage path.\n// - OOS-09 (prior loop): no `addChild` / `removeChild` / `removeChildren`\n// Commands API. Retired this feat: `world.addChild` / `world.removeChild`\n// / `world.reparent` ship in M3, plus the relationship hook above.\n// - OOS-01 (prior loop): no dangling-entity sweep on `array<entity>`. If a\n// child entity has been despawned, the stored u32 still occupies the\n// slot; AI users explicitly call the engine's entity-liveness check\n// before consuming a child id (see `world.get(child, ChildOf)` returns\n// `Result.err(...)` for a despawned entity --- the stored u32 surfaces\n// as a dead handle, not a silent zero). Charter proposition 4\n// (explicit failure): the engine does not silently drop dangling\n// entries.\n//\n// charter mapping: proposition 2 (Bevy ChildOf+Children pair, holder\n// perspective) + proposition 3 (machine-readable schema:\n// `componentSchema(Children).entities === 'array<entity>'`) + proposition 4 (explicit\n// failure: dangling entries surface to the AI user via `world.get(parent, Entity)` liveness probe,\n// not silent drop) + proposition 5 (consistent abstraction: Children is the\n// generic relationship-mirror shape, not a ChildOf special case).\n\nimport { defineRelationship } from '@forgeax/engine-ecs';\n\n/**\n * Hierarchy forward-list of child entities.\n *\n * `entities` is a variable-length `array<entity>` field; each element is\n * an `Entity` u32 the AI user pushed via\n * `world.push(parent, Children, 'entities', child)`. The value returned by\n * `world.get(parent, Children).unwrap().entities` is a read-only\n * `Uint32Array` snapshot rematerialised fresh on every read (D-4 no-cache);\n * the snapshot's `length` equals the live element count.\n *\n * Invariants:\n * - Children is NOT consumed by `propagateTransforms` (which walks ChildOf\n * upward, D-P2). The forward list is for AI-user traversal / debug /\n * inspection.\n * - Children <-> ChildOf consistency is maintained by the engine via the\n * ChildOf `relationship` mirror hook (see ./child-of.ts): adding /\n * removing / reparenting ChildOf on a child auto-updates the parent's\n * `entities` list. AI users do not hand-sync the two sides for the\n * hierarchy.\n * - Stored entity u32s are NOT auto-cleared when the referenced entity is\n * despawned (OOS-01 above); a `world.get(despawnedChild, ...)` call\n * returns `Result.err(...)` so AI users discover the dangling state\n * through the engine's structured-error channel.\n *\n * @example Spawn a parent and two children via ChildOf (engine maintains Children):\n * const parent = world.spawn({ component: Transform, data: identityXf() }).unwrap();\n * const a = world.spawn(\n * { component: Transform, data: identityXf() },\n * { component: ChildOf, data: { parent } },\n * ).unwrap();\n * const b = world.spawn(\n * { component: Transform, data: identityXf() },\n * { component: ChildOf, data: { parent } },\n * ).unwrap();\n * // Read back via the read-only snapshot - engine appended a, b:\n * const snap = world.get(parent, Children).unwrap().entities;\n * for (let i = 0; i < snap.length; i++) {\n * const child = snap[i];\n * // ... consume; world.get(child, ...) surfaces a structured error\n * // if the child has been despawned (OOS-01 dangling-entity surface).\n * }\n */\nexport const { source: ChildOf, target: Children } = defineRelationship({\n sourceName: 'ChildOf',\n sourceField: 'parent',\n targetName: 'Children',\n targetField: 'entities',\n exclusive: true,\n linkedSpawn: true,\n});\n","import { defineComponent } from '@forgeax/engine-ecs';\n\n/** Per-entity morph weights; length is validated against the mesh target count. */\nexport const MorphWeights = defineComponent('MorphWeights', {\n weights: { type: 'array<f32>' },\n});\n","// @forgeax/engine-runtime --- Name component (built-in identifier).\n//\n// Single-field minimal skeleton: { value: 'string' }. Bare 'string' schema\n// vocab keyword routes through ECS UniqueRefStore (D-R3 single-arm managed\n// dispatch); the read shape is a native JS string.\n//\n// Lives in `runtime` rather than `ecs` because Name is a built-in *component*,\n// not part of the ECS framework itself (it does not participate in archetype /\n// query / world mechanics like the essential `Entity` component does). Mirrors\n// Bevy's split: `Entity` lives in `bevy_ecs`; `Name` lives in `bevy_core`.\n//\n// Migrated from packages/ecs/src/name.ts by tweak-20260612-ecs-concept-compression\n// (architecture-principles.md §1 SSOT: Name's authoritative location is the\n// runtime built-in components surface, not the ECS framework barrel).\n//\n// Naming follows the Bevy-aligned convention locked by feat-20260513:\n// single-semantic component drops the 'Component' suffix (Transform / Camera\n// / DirectionalLight / Name).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\nexport const Name = defineComponent('Name', { value: { type: 'string' } });\n","// @forgeax/engine-runtime - Transform (local TRS + world mat4).\n//\n// Schema: three local array<f32, N> columns -- pos (3-vec position), quat\n// (4-quat rotation, component order [x, y, z, w]), scale (3-vec scale) --\n// plus one `world: array<f32, 16>` field carrying the resolved world-space\n// mat4 (column-major 16 floats, written by the propagate kernel each frame).\n// Inline stride-N array columns (feat-20260602) store each row's N floats\n// contiguously, so per-row xyz locality is native to the column layout; the\n// former per-axis scalar decomposition (10 f32 columns) predates\n// inline array columns and was retired in feat-20260709 M2.\n//\n// The world column is the SSOT for the resolved world transform: a root's\n// world equals its local mat4; a child's world equals parent.world x local.\n// AI users author the local TRS arrays and read the derived world mat4 via\n// the ECS column-level array view (`world.get(e, Transform).world` -> live\n// Float32Array of 16 column-major floats). They MUST NOT hand-write the\n// world column -- it is overwritten by propagate.\n//\n// charter mapping: P1 (progressive disclosure via defaults map -- AI users\n// spawn with data: {} and get identity local TRS + identity world mat4),\n// F1 (context-limited: single-import barrel; 3 array keys replace 10 scalar\n// keys at every spawn call-site), P4 (consistent abstraction: pos / quat /\n// scale array columns follow the same access pattern as `world` -- learn\n// the flat column form once, apply it to every array<f32, N> field),\n// P3 (machine-readable schema > prose).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n// Column-major identity mat4 (16 floats) used as the world-column default so\n// `spawn({ component: Transform, data: {} })` lands an identity world view\n// before the first propagate pass writes the resolved transform.\nconst IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);\n\n/**\n * Transform: local position `pos` (xyz), rotation `quat` (quaternion,\n * component order [x, y, z, w]), scale `scale` (xyz), plus the resolved\n * `world` mat4 (column-major 16 floats).\n *\n * Local TRS is stored as three inline stride-N array<f32, N> columns. The\n * propagate kernel composes each entity's local TRS into a mat4 and writes it\n * into the `world` column (root: world = local; child: world = parent.world x\n * local) using `@forgeax/engine-math` mat4 / vec3 / quat APIs (charter P4: do\n * not reinvent math). MVP recomposes every frame; dirty-flag optimization is\n * owned by feat-future-render-world.\n *\n * The `world` column is a fixed-capacity `array<f32, 16>` (feat-20260602):\n * the 16 contiguous floats live inline in a stride-16 column -- no BufferPool\n * slot. Read it via `world.get(e, Transform).world` which returns a live\n * `Float32Array` aliasing the column buffer. The view is transient: it aliases\n * the archetype column buffer and is valid only until the next structural\n * change (spawn / despawn / addComponent / removeComponent). Re-fetch the view\n * on every access; holding a view across a structural change is undefined\n * behaviour (the backing `ArrayBuffer` is detached on column growth, and\n * swap-remove at the same row index points to the wrong entity). All existing\n * hot paths (propagate / render-extract / pick) already comply -- see\n * `packages/ecs/README.md` Transient view contract section.\n *\n * All three local columns carry explicit layer-2 defaults (identity\n * transform): `pos: [0, 0, 0]`, `quat: [0, 0, 0, 1]` (identity quaternion,\n * [x, y, z, w]), `scale: [1, 1, 1]`. quat and scale MUST stay explicit: the\n * layer-3 fallback for array<f32, N> is all-zero, which would land an invalid\n * zero quaternion / zero scale. The `world` field defaults to the identity\n * mat4. AI users spawn with `data: {}` or with only the fields they need to\n * override.\n *\n * @example Minimal spawn (local defaulted to identity, world = identity mat4):\n * world.spawn({ component: Transform, data: {} });\n *\n * @example Override only the position, leaving rotation/scale at identity:\n * world.spawn({ component: Transform, data: { pos: [0, 6, 0] } });\n *\n * @example Full explicit local form (defaults are opt-in):\n * world.spawn({ component: Transform, data: {\n * pos: [1, 2, 3],\n * quat: [0, 0, 0, 1], // [x, y, z, w]\n * scale: [1, 1, 1],\n * } });\n */\nexport const Transform = defineComponent('Transform', {\n pos: { type: 'array<f32, 3>', default: new Float32Array([0, 0, 0]) },\n // Component order [x, y, z, w] end to end (glTF-aligned; E6).\n quat: { type: 'array<f32, 4>', default: new Float32Array([0, 0, 0, 1]) },\n scale: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n // `world` is field-level transient (D-5): scene collect skips it. The resolved\n // world mat4 is derived by the propagate kernel from the persisted local TRS\n // each frame, so serializing it would store reconstructable data (SSOT: local\n // TRS). Round-trip re-derives an equivalent world on the first propagate pass.\n world: { type: 'array<f32, 16>', default: IDENTITY_MAT4, transient: true },\n});\n","/** Immutable Scene collection policy shared by runtime collectors. */\nexport interface SceneCollectProfile {\n readonly includeComponent: (componentName: string, transient: boolean) => boolean;\n readonly includeField: (componentName: string, fieldName: string, transient: boolean) => boolean;\n}\n\nexport const SCENE_COLLECT_PROFILE: SceneCollectProfile = Object.freeze({\n includeComponent: (_componentName: string, transient: boolean) => !transient,\n includeField: (_componentName: string, _fieldName: string, transient: boolean) => !transient,\n});\n","import type { AssetRef, MountOverride, SceneAsset } from '@forgeax/engine-types';\nimport { err, ok, type Result } from '@forgeax/engine-types';\n\nexport type SceneComponentSchemaResolver = (\n componentName: string,\n) => Readonly<Record<string, string>> | undefined;\n\nexport interface SceneExternalizationError {\n readonly field: string;\n readonly value: unknown;\n}\n\nexport interface ExternalizedSceneAsset {\n readonly payload: Record<string, unknown>;\n readonly refs: readonly AssetRef[];\n}\n\nfunction sharedKind(type: string | undefined): 'one' | 'many' | undefined {\n if (type?.startsWith('shared<')) return 'one';\n if (type?.startsWith('array<shared<')) return 'many';\n return undefined;\n}\n\nfunction overrideGuids(\n override: MountOverride,\n resolveSchema: SceneComponentSchemaResolver,\n): readonly { field: string; guid: string }[] {\n const schema = resolveSchema(override.comp);\n const values =\n override.field !== undefined\n ? [[override.field, override.value] as const]\n : override.value !== null &&\n typeof override.value === 'object' &&\n !Array.isArray(override.value)\n ? Object.entries(override.value as Record<string, unknown>)\n : [];\n return values.flatMap(([field, value]) => {\n const kind = sharedKind(schema?.[field]);\n if (kind === 'one' && typeof value === 'string') return [{ field, guid: value }];\n if (kind === 'many' && Array.isArray(value)) {\n return value.flatMap((item) => (typeof item === 'string' ? [{ field, guid: item }] : []));\n }\n return [];\n });\n}\n\n/** Project a SceneAsset's shared asset fields into a payload plus indexed refs. */\nexport function externalizeSceneAsset(\n scene: SceneAsset,\n resolveSchema: SceneComponentSchemaResolver,\n): Result<ExternalizedSceneAsset, SceneExternalizationError> {\n const refs: AssetRef[] = [];\n const indexByGuid = new Map<string, number>();\n const addRef = (\n guid: string,\n sourceField: NonNullable<AssetRef['sourceField']>,\n sceneEntityId?: number,\n ): number => {\n const prior = indexByGuid.get(guid);\n if (prior !== undefined) return prior;\n const index = refs.length;\n refs.push({ guid, sourceField, ...(sceneEntityId === undefined ? {} : { sceneEntityId }) });\n indexByGuid.set(guid, index);\n return index;\n };\n\n const entities = scene.entities.map((entity) => {\n const components: Record<string, Record<string, unknown>> = {};\n for (const componentName of Object.keys(entity.components)) {\n const schema = resolveSchema(componentName);\n const source = entity.components[componentName] as Record<string, unknown> | undefined;\n if (source === undefined) continue;\n const fields: Record<string, unknown> = {};\n for (const fieldName of Object.keys(source)) {\n const value = source[fieldName];\n if (value === undefined) continue;\n const kind = sharedKind(schema?.[fieldName]);\n if (kind === 'one' && typeof value === 'string') {\n fields[fieldName] = addRef(value, { componentName, fieldName }, entity.localId as number);\n } else if (kind === 'many' && Array.isArray(value)) {\n fields[fieldName] = value.map((item, arrayIndex) =>\n typeof item === 'string'\n ? addRef(item, { componentName, fieldName, arrayIndex }, entity.localId as number)\n : item,\n );\n } else {\n fields[fieldName] = value;\n }\n }\n if (Object.keys(fields).length > 0 || Object.keys(schema ?? {}).length === 0) {\n components[componentName] = fields;\n }\n }\n return { localId: entity.localId as number, components };\n });\n\n const mounts = scene.mounts?.map((mount) => {\n const source =\n typeof mount.source === 'string'\n ? addRef(\n mount.source,\n { componentName: 'SceneInstance', fieldName: 'source' },\n mount.localId as number,\n )\n : (mount.source as number);\n for (const { field, guid } of (mount.overrides ?? []).flatMap((override) =>\n overrideGuids(override, resolveSchema),\n )) {\n addRef(guid, { componentName: 'SceneInstance', fieldName: `overrides.${field}` });\n }\n return {\n localId: mount.localId as number,\n source,\n memberFirst: mount.memberFirst as number,\n memberCount: mount.memberCount,\n ...(mount.parent === undefined ? {} : { parent: mount.parent as number }),\n ...(mount.publicationFence === undefined ? {} : { publicationFence: mount.publicationFence }),\n ...(mount.overrides === undefined\n ? {}\n : { overrides: mount.overrides.map((item) => ({ ...item })) }),\n };\n });\n for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {\n if (typeof guid !== 'string') return err({ field: 'skinGuids', value: guid });\n addRef(guid, { componentName: '<scene>', fieldName: 'skinGuids', arrayIndex });\n }\n return ok({\n payload: {\n entities,\n ...(mounts === undefined || mounts.length === 0 ? {} : { mounts }),\n ...(scene.skinGuids === undefined\n ? {}\n : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid) as number) }),\n },\n refs,\n });\n}\n","import {\n defineSystem,\n defineSystemSet,\n type EntityHandle,\n FixedUpdate,\n type SystemHandle,\n Update,\n type World,\n} from '@forgeax/engine-ecs';\nimport {\n createWorldProjection,\n setDerivedComponent,\n type WorldProjection,\n} from '@forgeax/engine-ecs/projection';\nimport { type Mat4, mat4 } from '@forgeax/engine-math';\nimport { err, ok, type Result } from '@forgeax/engine-types';\nimport { ChildOf } from '../components/child-of';\nimport { Transform } from '../components/transform';\nimport { SceneError } from '../errors';\nimport { projectHierarchy, type SceneHierarchySnapshot } from './hierarchy-projection';\n\nexport const PROPAGATE_TRANSFORMS_SYSTEM = 'propagateTransforms' as const;\nexport const PROPAGATE_TRANSFORMS_FIXED_SYSTEM = 'propagateTransformsFixed' as const;\nexport const TransformSet = defineSystemSet({ name: 'transform' });\nexport const TransformFixedSet = defineSystemSet({ name: 'transform-fixed' });\n\ninterface LocalState {\n readonly pos: Float32Array;\n readonly quat: Float32Array;\n readonly scale: Float32Array;\n}\n\ninterface PropagationCache {\n readonly projection: WorldProjection;\n readonly hierarchy: SceneHierarchySnapshot;\n readonly parentOf: ReadonlyMap<EntityHandle, EntityHandle>;\n readonly childrenOf: ReadonlyMap<EntityHandle, readonly EntityHandle[]>;\n readonly entities: ReadonlySet<EntityHandle>;\n readonly locals: Map<EntityHandle, LocalState>;\n readonly derived: Map<EntityHandle, Float32Array>;\n result: Result<void, SceneError>;\n}\n\nconst CACHE = new WeakMap<World, PropagationCache>();\n\ninterface TransformRegistrationLease {\n refs: number;\n}\n\n// A World can be consumed by more than one renderer/view. Keep the schedule\n// registration leased per World so one owner disposing cannot remove the\n// shared transform system from another owner. This is lifecycle bookkeeping,\n// not a second component/system authority; the World schedule remains the\n// source of truth.\nconst REGISTRATION_LEASES = new WeakMap<World, TransformRegistrationLease>();\n\nfunction copyState(value: {\n readonly pos: ArrayLike<number>;\n readonly quat: ArrayLike<number>;\n readonly scale: ArrayLike<number>;\n}): LocalState {\n return {\n pos: new Float32Array(value.pos),\n quat: new Float32Array(value.quat),\n scale: new Float32Array(value.scale),\n };\n}\n\nfunction sameArray(left: ArrayLike<number>, right: ArrayLike<number>): boolean {\n if (left.length !== right.length) return false;\n for (let index = 0; index < left.length; index += 1) {\n if (left[index] !== right[index]) return false;\n }\n return true;\n}\n\nfunction sameState(left: LocalState | undefined, right: LocalState): boolean {\n return (\n left !== undefined &&\n sameArray(left.pos, right.pos) &&\n sameArray(left.quat, right.quat) &&\n sameArray(left.scale, right.scale)\n );\n}\n\nfunction compose(state: LocalState, out: Mat4): void {\n mat4.compose(out, state.pos, state.quat, state.scale);\n}\n\nfunction indexChildren(\n hierarchy: SceneHierarchySnapshot,\n): ReadonlyMap<EntityHandle, readonly EntityHandle[]> {\n const childrenOf = new Map<EntityHandle, EntityHandle[]>();\n for (const [child, parent] of hierarchy.parentOf) {\n const children = childrenOf.get(parent);\n if (children === undefined) childrenOf.set(parent, [child]);\n else children.push(child);\n }\n return childrenOf;\n}\n\nfunction descendants(\n childrenOf: ReadonlyMap<EntityHandle, readonly EntityHandle[]>,\n roots: ReadonlySet<EntityHandle>,\n): Set<EntityHandle> {\n const affected = new Set(roots);\n const pending = [...roots];\n for (let index = 0; index < pending.length; index += 1) {\n const parent = pending[index];\n if (parent === undefined) continue;\n for (const child of childrenOf.get(parent) ?? []) {\n if (affected.has(child)) continue;\n affected.add(child);\n pending.push(child);\n }\n }\n return affected;\n}\n\nfunction hierarchyError(hierarchy: SceneHierarchySnapshot): Result<void, SceneError> {\n const first = hierarchy.diagnostics[0];\n if (first === undefined) return ok(undefined);\n return err(\n new SceneError({\n code: first.code,\n expected: first.expected,\n hint: first.hint,\n detail: first.detail,\n }),\n );\n}\n\nfunction buildCache(world: World, projection: WorldProjection): PropagationCache {\n const hierarchy = projectHierarchy(world);\n const locals = new Map<EntityHandle, LocalState>();\n const entities = new Set<EntityHandle>();\n const query = world.query({ read: [Transform], optional: [ChildOf] });\n if (query.ok) {\n for (const row of query.value) {\n const transform = row.get(Transform);\n if (transform === undefined) continue;\n entities.add(row.entity);\n locals.set(row.entity, copyState(transform));\n }\n }\n return {\n projection,\n hierarchy,\n parentOf: hierarchy.parentOf,\n childrenOf: indexChildren(hierarchy),\n entities,\n locals,\n derived: new Map(),\n result: ok(undefined),\n };\n}\n\nfunction deriveEntity(\n world: World,\n entity: EntityHandle,\n cache: PropagationCache,\n affected: ReadonlySet<EntityHandle>,\n visiting: Set<EntityHandle>,\n published: Set<EntityHandle>,\n): Result<void, SceneError> {\n if (!affected.has(entity) || cache.derived.has(entity)) return ok(undefined);\n if (visiting.has(entity)) return ok(undefined);\n visiting.add(entity);\n const local = cache.locals.get(entity);\n if (local === undefined) {\n visiting.delete(entity);\n return ok(undefined);\n }\n const parent = cache.parentOf.get(entity);\n if (parent !== undefined && cache.entities.has(parent)) {\n const parentResult = deriveEntity(world, parent, cache, affected, visiting, published);\n if (!parentResult.ok) return parentResult;\n }\n const localWorld = mat4.create();\n compose(local, localWorld);\n const parentWorld = parent === undefined ? undefined : cache.derived.get(parent);\n const resolved = mat4.create();\n if (parentWorld === undefined) resolved.set(localWorld);\n else mat4.multiply(resolved, parentWorld, localWorld);\n const write = setDerivedComponent(world, entity, Transform, { world: resolved });\n if (!write.ok) {\n visiting.delete(entity);\n return err(\n new SceneError({\n code: 'hierarchy-broken',\n expected: 'the derived Transform.world write to succeed',\n hint: 'inspect the ECS mutation error before retrying transform propagation',\n detail: { entity, parent: entity },\n }),\n );\n }\n cache.derived.set(entity, resolved);\n published.add(entity);\n visiting.delete(entity);\n return ok(undefined);\n}\n\nfunction drainPublishedChanges(\n cache: PropagationCache,\n published: ReadonlySet<EntityHandle>,\n): boolean {\n const drained = cache.projection.poll();\n if (drained.status === 'rebuild') return true;\n return drained.changes.every(\n (change) =>\n change.kind === 'derived-component-changed' &&\n change.component === Transform &&\n published.has(change.entity),\n );\n}\n\nexport function propagateTransforms(\n world: World,\n _hierarchy?: SceneHierarchySnapshot,\n): Result<void, SceneError> {\n let cache = CACHE.get(world);\n if (cache === undefined) {\n cache = buildCache(world, createWorldProjection(world, { components: [Transform, ChildOf] }));\n CACHE.set(world, cache);\n const initial = new Set(cache.entities);\n const published = new Set<EntityHandle>();\n for (const entity of initial) {\n const result = deriveEntity(world, entity, cache, initial, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n }\n\n const evidence = cache.projection.poll();\n if (evidence.status === 'rebuild') {\n cache = buildCache(world, cache.projection);\n CACHE.set(world, cache);\n const all = new Set(cache.entities);\n const published = new Set<EntityHandle>();\n for (const entity of all) {\n const result = deriveEntity(world, entity, cache, all, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n }\n if (evidence.changes.length === 0) return cache.result;\n\n const transformSeeds = new Set<EntityHandle>();\n let rebuild = false;\n for (const change of evidence.changes) {\n if (\n change.kind === 'entity-removed' ||\n change.kind === 'component-added' ||\n change.kind === 'component-removed'\n ) {\n rebuild = true;\n break;\n }\n if (change.kind !== 'component-changed') continue;\n if (change.component === ChildOf) {\n rebuild = true;\n break;\n }\n // Derived Transform publications are deliberately not dirty seeds. Only\n // authored component-changed records can invalidate local TRS state.\n if (change.component === Transform) transformSeeds.add(change.entity);\n }\n if (rebuild) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n\n // A World can record several authored writes before this pass. Read each\n // seed once so the final local state decides whether propagation is needed.\n const changed = new Set<EntityHandle>();\n for (const entity of transformSeeds) {\n const current = world.get(entity, Transform);\n if (!current.ok) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n const next = copyState(current.value);\n if (!sameState(cache.locals.get(entity), next)) changed.add(entity);\n cache.locals.set(entity, next);\n }\n if (changed.size === 0) return cache.result;\n\n const affected = descendants(cache.childrenOf, changed);\n const published = new Set<EntityHandle>();\n for (const entity of affected) cache.derived.delete(entity);\n for (const entity of affected) {\n const result = deriveEntity(world, entity, cache, affected, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n}\n\nexport const PropagateTransforms: SystemHandle<readonly []> = defineSystem({\n name: PROPAGATE_TRANSFORMS_SYSTEM,\n queries: [],\n fn: (world) => {\n const result = propagateTransforms(world);\n if (!result.ok) throw result.error;\n },\n});\n\nexport const PropagateTransformsFixed: SystemHandle<readonly []> = defineSystem({\n name: PROPAGATE_TRANSFORMS_FIXED_SYSTEM,\n queries: [],\n fn: PropagateTransforms.fn,\n});\n\nexport function registerPropagateTransforms(\n world: World,\n options: { beforeSystemName?: string } = {},\n): () => void {\n const existing = REGISTRATION_LEASES.get(world);\n if (existing !== undefined) {\n existing.refs += 1;\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n existing.refs -= 1;\n if (existing.refs === 0) {\n world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);\n world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);\n REGISTRATION_LEASES.delete(world);\n CACHE.delete(world);\n }\n };\n }\n if (options.beforeSystemName === undefined) {\n world.addSystems(Update, TransformSet, [PropagateTransforms]).unwrap();\n } else {\n world\n .addSystems(Update, TransformSet, [\n {\n name: PROPAGATE_TRANSFORMS_SYSTEM,\n queries: [],\n fn: PropagateTransforms.fn,\n before: [options.beforeSystemName],\n },\n ])\n .unwrap();\n }\n world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();\n const lease: TransformRegistrationLease = { refs: 1 };\n REGISTRATION_LEASES.set(world, lease);\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n lease.refs -= 1;\n if (lease.refs !== 0) return;\n world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);\n world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);\n REGISTRATION_LEASES.delete(world);\n CACHE.delete(world);\n };\n}\n","import { Entity, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport { createWorldProjection } from '@forgeax/engine-ecs/projection';\nimport { ChildOf } from '../components/child-of';\nimport type { SceneErrorCode, SceneErrorDetail } from '../errors';\n\nexport interface SceneHierarchyDiagnostic {\n readonly code: SceneErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: SceneErrorDetail;\n}\n\nexport interface SceneHierarchySnapshot {\n readonly parentOf: ReadonlyMap<EntityHandle, EntityHandle>;\n readonly diagnostics: readonly SceneHierarchyDiagnostic[];\n getParent(entity: EntityHandle): EntityHandle | undefined;\n}\n\ninterface HierarchyProjectionCacheEntry {\n readonly changes: ReturnType<typeof createWorldProjection>;\n readonly snapshot: SceneHierarchySnapshot;\n}\n\n// A World can be observed by the transform system, renderer visibility, and\n// editor projections in the same frame. Keep one World-local projection so\n// those consumers do not each rescan every archetype. Structure changes and\n// the ChildOf component's own mutation token are the invalidation keys; the\n// global mutation epoch is intentionally too broad because animation and\n// runtime-only component writes may advance it every frame. Direct table\n// writes are internal-only and must not mutate authored hierarchy state.\nconst HIERARCHY_PROJECTION_CACHE = new WeakMap<World, HierarchyProjectionCacheEntry>();\n\nfunction diagnostic(\n code: SceneErrorCode,\n entity: EntityHandle,\n parent: EntityHandle,\n): SceneHierarchyDiagnostic {\n if (code === 'hierarchy-cycle') {\n return {\n code,\n expected: 'ChildOf parent edges form an acyclic live hierarchy',\n hint: 'remove one ChildOf edge from the reported cycle, then re-run the extract',\n detail: { entity, parent },\n };\n }\n return {\n code,\n expected: 'ChildOf.parent references a live entity in the same World',\n hint: 'remove the stale ChildOf component or restore the referenced parent in this World',\n detail: { entity, parent },\n };\n}\n\n/** Build the only World-local projection of ChildOf parent facts. */\nexport function projectHierarchy(world: World): SceneHierarchySnapshot {\n const cached = HIERARCHY_PROJECTION_CACHE.get(world);\n const changes = cached?.changes ?? createWorldProjection(world, { components: [ChildOf] });\n if (cached !== undefined) {\n const evidence = changes.poll();\n if (evidence.status === 'delta' && evidence.changes.length === 0) return cached.snapshot;\n }\n const liveEntities = new Set<EntityHandle>();\n const authoredParents = new Map<EntityHandle, EntityHandle>();\n\n const query = world.query({ read: [Entity], optional: [ChildOf] });\n if (query.ok) {\n for (const row of query.value) {\n liveEntities.add(row.entity);\n const parent = row.get(ChildOf)?.parent;\n if (parent !== undefined && parent !== null) authoredParents.set(row.entity, parent);\n }\n }\n\n const parentOf = new Map<EntityHandle, EntityHandle>();\n const diagnostics: SceneHierarchyDiagnostic[] = [];\n for (const [entity, parent] of authoredParents) {\n if (liveEntities.has(parent)) {\n parentOf.set(entity, parent);\n } else {\n diagnostics.push(diagnostic('hierarchy-broken', entity, parent));\n }\n }\n\n const state = new Map<EntityHandle, 0 | 1 | 2>();\n const stack: EntityHandle[] = [];\n const cycleMembers = new Set<EntityHandle>();\n const visit = (entity: EntityHandle): void => {\n const currentState = state.get(entity) ?? 0;\n if (currentState === 2) return;\n if (currentState === 1) {\n const cycleStart = stack.indexOf(entity);\n for (let index = cycleStart; index >= 0 && index < stack.length; index++) {\n const member = stack[index];\n if (member !== undefined) cycleMembers.add(member);\n }\n return;\n }\n\n state.set(entity, 1);\n stack.push(entity);\n const parent = parentOf.get(entity);\n if (parent !== undefined) visit(parent);\n stack.pop();\n state.set(entity, 2);\n };\n\n for (const entity of liveEntities) visit(entity);\n for (const entity of cycleMembers) {\n const parent = authoredParents.get(entity);\n if (parent !== undefined) diagnostics.push(diagnostic('hierarchy-cycle', entity, parent));\n parentOf.delete(entity);\n }\n\n diagnostics.sort((left, right) => {\n const entityDelta = (left.detail.entity as number) - (right.detail.entity as number);\n if (entityDelta !== 0) return entityDelta;\n return left.code.localeCompare(right.code);\n });\n\n const stableParentOf = new Map(parentOf);\n const stableDiagnostics = Object.freeze(diagnostics.slice());\n const snapshot: SceneHierarchySnapshot = {\n parentOf: stableParentOf,\n diagnostics: stableDiagnostics,\n getParent(entity: EntityHandle): EntityHandle | undefined {\n return stableParentOf.get(entity);\n },\n };\n HIERARCHY_PROJECTION_CACHE.set(world, {\n changes,\n snapshot,\n });\n return snapshot;\n}\n","import type { Component, World } from '@forgeax/engine-ecs';\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { ChildOf } from './components/child-of';\nimport { Children } from './components/children';\nimport { MorphWeights } from './components/morph-weights';\nimport { Name } from './components/name';\nimport { Transform } from './components/transform';\nimport { registerPropagateTransforms } from './systems/propagate-transforms';\n\nconst SCENE_COMPONENTS: readonly Component[] = [ChildOf, Children, MorphWeights, Name, Transform];\n\nfunction registerSceneComponents(world: World): () => void {\n const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n\nexport function scenePlugin(): Plugin {\n return {\n name: 'scene',\n inject: ['world'],\n apply(ctx) {\n ctx.effect(() => registerSceneComponents(ctx.world), 'scene/components');\n ctx.effect(() => registerPropagateTransforms(ctx.world), 'scene/propagate-transforms');\n },\n };\n}\n"],"mappings":"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","names":["componentSchema","componentSchema","value","defineComponent","defineComponent","createWorldProjection","createWorldProjection","published","active"]}
1
+ {"version":3,"sources":["../../types/src/asset-errors.ts","../../types/src/result.ts","../../types/src/asset-evidence.ts","../../types/src/handle.ts","../../types/src/material/asset.ts","../../types/src/material/color-space.ts","../../types/src/material/errors.ts","../../types/src/material/resolve.ts","../../types/src/runtime-scope.ts","../../types/src/derive-paramschema.ts","../../types/src/asset-producer.ts","../../types/src/catalog.ts","../../types/src/import.ts","../../types/src/index.ts","../src/assets/scene-decoder.ts","../src/assets/scene-projection.ts","../src/instances/scene-instances.ts","../src/errors.ts","../src/components/children.ts","../src/components/morph-weights.ts","../src/components/name.ts","../src/components/transform.ts","../src/instances/collect-profile.ts","../src/instances/externalization.ts","../src/systems/propagate-transforms.ts","../src/systems/hierarchy-projection.ts","../src/plugin.ts"],"sourcesContent":["/** Closed structured error contracts for Pack v2 and asset evidence. */\n\nexport type AssetLoadErrorCode =\n | 'asset-guid-invalid'\n | 'asset-kind-mismatch'\n | 'asset-not-found'\n | 'asset-not-ready'\n | 'catalog-unavailable'\n | 'catalog-discontinuous'\n | 'asset-fetch-failed'\n | 'asset-integrity-failed'\n | 'asset-package-invalid'\n | 'asset-decoder-missing'\n | 'asset-decode-failed'\n | 'asset-dependency-failed'\n | 'asset-superseded'\n | 'asset-load-cancelled'\n | 'asset-runtime-disposed';\n\nexport const ASSET_LOAD_ERROR_HINTS: Readonly<Record<AssetLoadErrorCode, string>> = {\n 'asset-guid-invalid': 'provide a valid asset GUID and retry the current publication',\n 'asset-kind-mismatch': 'pass the Catalog kind or the matching custom AssetKind token',\n 'asset-not-found': 'inspect the producer Catalog and rebuild the missing publication',\n 'asset-not-ready': 'wait for the current publication or inspect its producer lifecycle',\n 'catalog-unavailable': 'inspect the scope and create a fresh Registry for a new scope',\n 'catalog-discontinuous': 'reconcile the Catalog baseline before loading the current row',\n 'asset-fetch-failed': 'verify the package locator and republish the Pack',\n 'asset-integrity-failed': 'verify the artifact digest and recook the Pack',\n 'asset-package-invalid': 'validate the Pack v2 envelope and recook invalid output',\n 'asset-decoder-missing': 'install the owner decoder lease for this kind',\n 'asset-decode-failed': 'inspect the structured decoder detail and repair the owner output',\n 'asset-dependency-failed': 'repair the dependency publication named in detail and retry',\n 'asset-superseded': 'load the current publication instead of the superseded ticket',\n 'asset-load-cancelled': 'retry with a live AbortSignal when the request is still needed',\n 'asset-runtime-disposed': 'obtain a new Registry from the current realm',\n};\n\ntype AssetLoadErrorBase<C extends AssetLoadErrorCode, D> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: D;\n};\n\nexport type AssetLoadError =\n | AssetLoadErrorBase<'asset-guid-invalid', { readonly guid: string }>\n | AssetLoadErrorBase<\n 'asset-kind-mismatch',\n { readonly guid: string; readonly expectedKind: string; readonly actualKind: string }\n >\n | AssetLoadErrorBase<'asset-not-found', { readonly guid: string }>\n | AssetLoadErrorBase<'asset-not-ready', { readonly guid: string; readonly generation: number }>\n | AssetLoadErrorBase<'catalog-unavailable', { readonly scopeId: string }>\n | AssetLoadErrorBase<\n 'catalog-discontinuous',\n {\n readonly scopeId: string;\n readonly expectedGeneration: number;\n readonly actualGeneration: number;\n }\n >\n | AssetLoadErrorBase<'asset-fetch-failed', { readonly guid: string; readonly packageUrl: string }>\n | AssetLoadErrorBase<\n 'asset-integrity-failed',\n {\n readonly guid: string;\n readonly artifactKey: string;\n readonly expectedDigest: string;\n readonly actualDigest: string;\n }\n >\n | AssetLoadErrorBase<'asset-package-invalid', { readonly guid: string; readonly reason: string }>\n | AssetLoadErrorBase<'asset-decoder-missing', { readonly kind: string }>\n | AssetLoadErrorBase<'asset-decode-failed', { readonly guid: string; readonly kind: string }>\n | AssetLoadErrorBase<\n 'asset-dependency-failed',\n { readonly guid: string; readonly dependencyGuid: string }\n >\n | AssetLoadErrorBase<'asset-superseded', { readonly guid: string; readonly generation: number }>\n | AssetLoadErrorBase<'asset-load-cancelled', { readonly guid: string }>\n | AssetLoadErrorBase<'asset-runtime-disposed', { readonly scopeId: string }>;\n\nexport type AssetArtifactErrorCode =\n | 'asset-artifact-path-invalid'\n | 'asset-artifact-missing'\n | 'asset-artifact-integrity-mismatch'\n | 'asset-artifact-media-unsupported'\n | 'asset-artifact-codec-unsupported'\n | 'asset-artifact-encoding-unsupported'\n | 'asset-artifact-decode-failed';\n\nexport type AssetArtifactErrorDetail =\n | {\n readonly guid: string;\n readonly artifactKey: string;\n readonly observed: string;\n readonly expected: string;\n }\n | {\n readonly guid: string;\n readonly artifactKey: string;\n readonly observed: string;\n readonly expected: string;\n readonly path: string;\n };\n\nexport type AssetArtifactError =\n | {\n readonly code: 'asset-artifact-path-invalid';\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly artifactKey: string }>;\n }\n | {\n readonly code: 'asset-artifact-missing';\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly path: string }>;\n }\n | {\n readonly code: Exclude<\n AssetArtifactErrorCode,\n 'asset-artifact-path-invalid' | 'asset-artifact-missing'\n >;\n readonly expected: string;\n readonly hint: string;\n readonly detail: Extract<AssetArtifactErrorDetail, { readonly artifactKey: string }>;\n };\n\nexport type PackV2ErrorCode =\n | 'pack-v2-version-unsupported'\n | 'pack-v2-envelope-invalid'\n | 'pack-v2-duplicate-guid'\n | 'pack-v2-duplicate-artifact-key'\n | 'pack-v2-artifact-descriptor-invalid';\n\nexport type PackV2ErrorDetail =\n | { readonly observed: string; readonly expected: string }\n | { readonly guid: string; readonly paths: readonly string[] }\n | { readonly guid: string; readonly artifactKey: string }\n | { readonly guid: string; readonly artifactKey: string; readonly field: string };\n\nexport interface PackV2Error {\n readonly code: PackV2ErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: PackV2ErrorDetail;\n}\n\nexport type AssetEvidenceErrorDetail =\n | { readonly capability: string; readonly stage: string }\n | { readonly guid: string; readonly observed: string; readonly expected: string };\n\nexport interface AssetEvidenceError {\n readonly code: AssetEvidenceErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AssetEvidenceErrorDetail;\n}\n\nexport const ASSET_EVIDENCE_ERROR_HINTS = {\n 'asset-evidence-capability-missing':\n 'provide the missing evidence capability, then rerun asset lookup or verify',\n 'asset-evidence-source-conflict':\n 'keep one source declaration per GUID and rerun the offline evidence projection',\n 'asset-evidence-locator-conflict':\n 'keep one packageUrl per GUID and rebuild the catalog before verifying the asset',\n 'asset-evidence-receipt-conflict':\n 'keep one producer-owned receipt per GUID and rerun cook before verifying the asset',\n 'asset-evidence-digest-mismatch':\n 'recook the source or restore the package bytes, then rerun artifact verification',\n} satisfies Readonly<Record<string, string>>;\n\nexport type AssetEvidenceErrorCode = keyof typeof ASSET_EVIDENCE_ERROR_HINTS;\n\n/** Ordered author-to-runtime stages used by structured recovery errors. */\nexport type AssetErrorStage =\n | 'author-validation'\n | 'external-declaration'\n | 'import'\n | 'native-cook'\n | 'ddc-validation'\n | 'runtime-parse'\n | 'editor-capability';\n\n/** AI-readable next action; it does not grant a cache or runtime write authority. */\nexport interface AssetStageRecovery {\n readonly action: string;\n readonly command?: string;\n readonly retryable: boolean;\n}\n\nexport type AssetStageErrorDetail =\n | { readonly authoringPath: string; readonly rule: string }\n | { readonly sourceKey: string; readonly sourceIndex?: number }\n | { readonly sourcePath: string; readonly importer?: string }\n | { readonly guid: string; readonly producer: string }\n | { readonly guid: string; readonly observedDigest: string; readonly expectedDigest: string }\n | { readonly guid: string; readonly packageUrl: string }\n | { readonly capability: string; readonly assetKind: string };\n\nexport interface AssetStageErrorBase<S extends AssetErrorStage, C extends string> {\n readonly stage: S;\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: AssetStageErrorDetail;\n readonly recovery: AssetStageRecovery;\n}\n\ntype AssetStageErrorWithDetail<\n S extends AssetErrorStage,\n C extends string,\n D extends AssetStageErrorDetail,\n> = Omit<AssetStageErrorBase<S, C>, 'detail'> & { readonly detail: D };\n\nexport type AuthorValidationError = AssetStageErrorWithDetail<\n 'author-validation',\n 'author-validation-failed',\n Extract<AssetStageErrorDetail, { readonly authoringPath: string }>\n>;\nexport type ExternalDeclarationError = AssetStageErrorWithDetail<\n 'external-declaration',\n 'external-declaration-invalid',\n Extract<AssetStageErrorDetail, { readonly sourceKey: string }>\n>;\nexport type ImportStageError = AssetStageErrorWithDetail<\n 'import',\n 'import-failed',\n Extract<AssetStageErrorDetail, { readonly sourcePath: string }>\n>;\nexport type NativeCookError = AssetStageErrorWithDetail<\n 'native-cook',\n 'native-cook-failed',\n Extract<AssetStageErrorDetail, { readonly guid: string; readonly producer: string }>\n>;\nexport type DdcValidationError = AssetStageErrorWithDetail<\n 'ddc-validation',\n 'ddc-validation-failed',\n Extract<AssetStageErrorDetail, { readonly observedDigest: string }>\n>;\nexport type RuntimeParseError = AssetStageErrorWithDetail<\n 'runtime-parse',\n 'runtime-parse-failed',\n Extract<AssetStageErrorDetail, { readonly packageUrl: string }>\n>;\nexport type EditorCapabilityError = AssetStageErrorWithDetail<\n 'editor-capability',\n 'editor-capability-unavailable',\n Extract<AssetStageErrorDetail, { readonly capability: string }>\n>;\n\nexport type AssetStageError =\n | AuthorValidationError\n | ExternalDeclarationError\n | ImportStageError\n | NativeCookError\n | DdcValidationError\n | RuntimeParseError\n | EditorCapabilityError;\n\nexport type AssetStageErrorCode = AssetStageError['code'];\n\nexport const ASSET_STAGE_ERROR_HINTS: Readonly<Record<AssetStageErrorCode, string>> = {\n 'author-validation-failed': 'read the authoring rule and apply the suggested recovery',\n 'external-declaration-invalid': 'repair the sourceKey declaration and retry recovery',\n 'import-failed': 'fix the importer input or registration, then retry recovery',\n 'native-cook-failed': 'fix the native producer and rerun recovery',\n 'ddc-validation-failed': 'repair the cooked artifact and rerun recovery',\n 'runtime-parse-failed': 'repair the package payload and rerun recovery',\n 'editor-capability-unavailable': 'register the capability and retry recovery',\n};\n","// @forgeax/engine-types — Result<T, E> SSOT (tweak-20260612-result-into-types).\n//\n// `Result<T, E>` is the project-wide binary success/failure carrier. It used to\n// live as TWO byte-aligned copies (`packages/rhi/src/errors.ts` +\n// `packages/ecs/src/result.ts`) — that \"byte-for-byte aligned\" prose was a\n// declaration, not a mechanism, and silently drifted. Consolidating here:\n// - One physical source for the discriminated union + `ok`/`err` factories.\n// - rhi / ecs each keep their typed error class (RhiError / EcsError union)\n// and just re-export the Result shape from this module.\n// - Generic parameter is intentionally NOT defaulted — each consumer narrows\n// the error parameter at its own boundary (`Result<T, RhiError>`,\n// `Result<T, EcsError>`).\n//\n// Charter mapping: P5 consistent abstraction (single Result idiom across rhi\n// and ecs); SSOT data layer (one authoritative carrier, derive don't duplicate).\n\n// ────────────────────────────────────────────────────────────────────────────\n// Narrow shape — boolean discriminant `.ok` + `.value` / `.error` branches\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Success branch — plain field access (`.ok === true`, `.value: T`) plus\n * method chain (`.unwrap()` / `.unwrapOr(default)`).\n *\n * On the ok branch `unwrap()` returns `.value`; `unwrapOr(d)` ignores the\n * default and returns `.value` (charter proposition 4 explicit-failure: the\n * throwing path lives only on the err branch).\n */\nexport interface ResultOk<T> {\n readonly ok: true;\n readonly value: T;\n /** Return the wrapped value. Never throws on the ok branch. */\n unwrap(): T;\n /** Return the wrapped value; the default argument is unused on the ok branch. */\n unwrapOr(defaultValue: T): T;\n}\n\n/**\n * Failure branch — plain field access (`.ok === false`, `.error: E`) plus\n * method chain (`.unwrap()` / `.unwrapOr(default)`).\n *\n * On the err branch `unwrap()` throws the underlying `E` (not wrapped in a\n * fresh `Error`) so AI consumers preserve `.code` / `.expected` / `.hint` for\n * programmatic recovery (charter proposition 4 + AGENTS.md \"Errors are\n * structured. Return Result, never throw for expected failures.\" — `.unwrap()`\n * is the explicit Layer 3 ErrorHandler boundary, not a hidden throw).\n *\n * `unwrapOr(d)` returns the default; the original error is silently dropped.\n * Use `if (!r.ok) ...` plus `r.error` if you need to inspect the failure.\n */\nexport interface ResultErr<E> {\n readonly ok: false;\n readonly error: E;\n /** Throw the underlying `error` (preserved without rewrapping). */\n unwrap(): never;\n /** Return the supplied default value (the original error is silently dropped). */\n unwrapOr<T>(defaultValue: T): T;\n}\n\n/**\n * Discriminated union: either `ResultOk<T>` or `ResultErr<E>`.\n *\n * Use `ok(value)` / `err(error)` factories to create instances.\n * Use `if (r.ok) ...` / `if (!r.ok) ...` to narrow.\n *\n * Width-assignment friendly: a `ResultErr<E>` returned by `err(...)` typed as\n * `Result<never, E>` satisfies any `Result<X, E>` after a `if (!r.ok)` narrow,\n * so `return r;` propagates without a cast.\n */\nexport type Result<T, E> = ResultOk<T> | ResultErr<E>;\n\n// ────────────────────────────────────────────────────────────────────────────\n// Prototype objects (shared methods — dimorphic, V8 friendly)\n// ────────────────────────────────────────────────────────────────────────────\n\nconst OK_PROTO = {\n unwrap(this: ResultOk<unknown>): unknown {\n return this.value;\n },\n unwrapOr(this: ResultOk<unknown>, _defaultValue: unknown): unknown {\n return this.value;\n },\n};\n\nconst ERR_PROTO = {\n unwrap(this: ResultErr<unknown>): never {\n // Throw the ORIGINAL error — NOT wrapped in new Error()\n throw this.error;\n },\n unwrapOr<T>(this: ResultErr<unknown>, defaultValue: T): T {\n return defaultValue;\n },\n};\n\n// ────────────────────────────────────────────────────────────────────────────\n// Factory functions\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Construct a success branch.\n *\n * Returns the narrow `ResultOk<T>` shape so direct `.value` access works\n * without a `if (r.ok)` narrow at the call site (a `ResultOk<T>` widens to\n * `Result<T, E>` for any `E` by structural assignment).\n */\nexport function ok<T>(value: T): ResultOk<T> {\n const r = Object.create(OK_PROTO) as { ok: true; value: T };\n r.ok = true;\n r.value = value;\n return r as ResultOk<T>;\n}\n\n/**\n * Construct a failure branch.\n *\n * Returns the narrow `ResultErr<E>` shape so direct `.error` access works\n * without a `if (!r.ok)` narrow at the call site. After a `if (!r.ok) return\n * r;` narrow, `ResultErr<E>` widens structurally to any `Result<X, E>` so\n * `return r;` propagates without a cast.\n */\nexport function err<E>(error: E): ResultErr<E> {\n const r = Object.create(ERR_PROTO) as { ok: false; error: E };\n r.ok = false;\n r.error = error;\n return r as ResultErr<E>;\n}\n","import type {\n ArtifactDescriptor,\n ArtifactVerificationStatus,\n ContentEncoding,\n CookFreshness,\n CookOrigin,\n CookReceiptStatus,\n CookStatus,\n Integrity,\n RuntimeEvidenceStatus,\n} from './asset.js';\nimport {\n ASSET_EVIDENCE_ERROR_HINTS,\n type AssetEvidenceError,\n type AssetEvidenceErrorCode,\n} from './asset-errors.js';\nimport { err, ok, type Result } from './result.js';\n\nexport type {\n AssetEvidenceError,\n AssetEvidenceErrorCode,\n AssetEvidenceErrorDetail,\n} from './asset-errors.js';\n\n/** Producer-owned cook attempt; a receipt is evidence only for its own input fingerprint. */\nexport interface CookReceipt {\n readonly guid: string;\n readonly origin: CookOrigin;\n readonly status: CookReceiptStatus;\n readonly inputFingerprint: string;\n readonly outputDigest?: string;\n readonly error?: {\n readonly code: string;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: unknown;\n };\n}\n\nexport type { CookProduct } from './asset.js';\n\n/** Source-side declaration used to compare current input with a producer receipt. */\nexport interface SourceDeclarationEvidence {\n readonly origin: CookOrigin;\n readonly sourcePath?: string;\n readonly inputFingerprint?: string;\n}\n\n/** Verification state for the Pack v2 package envelope, independent of cook freshness. */\nexport interface PackageVerificationEvidence {\n readonly status: ArtifactVerificationStatus;\n readonly digest?: string;\n}\n\n/** Descriptor plus the explicit verification result for one package artifact. */\nexport interface AssetEvidenceArtifact {\n readonly descriptor: ArtifactDescriptor;\n readonly verification: ArtifactVerificationStatus;\n}\n\nexport type AssetEvidenceStatus = 'passed' | 'notChecked' | 'failed' | 'unknown';\n\n/** Derived GUID evidence; use the closed states literally and follow error hints. */\nexport interface AssetEvidence {\n readonly guid: string;\n readonly packageUrl?: string;\n readonly cookReceiptUrl?: string;\n readonly source?: SourceDeclarationEvidence;\n readonly cook: {\n readonly status: CookStatus;\n readonly freshness: CookFreshness;\n readonly receipt?: CookReceipt;\n };\n readonly package?: PackageVerificationEvidence;\n readonly artifacts: Readonly<Record<string, AssetEvidenceArtifact>>;\n readonly runtime: {\n readonly status: RuntimeEvidenceStatus;\n };\n}\n\n/** Catalog navigation only; the URLs locate evidence but do not prove readiness. */\nexport interface AssetEvidenceLocator {\n readonly packageUrl: string;\n readonly cookReceiptUrl?: string;\n}\n\nexport interface AssetEvidencePackageInput {\n readonly guid: string;\n readonly digest?: string;\n readonly artifacts: Readonly<\n Record<\n string,\n {\n readonly descriptor: ArtifactDescriptor;\n readonly verification?: ArtifactVerificationStatus;\n }\n >\n >;\n}\n\n/** Inputs accepted by the pure projector; producers supply facts, not inferred status. */\nexport interface AssetEvidenceInputs {\n readonly guid: string;\n readonly source?: SourceDeclarationEvidence;\n readonly sources?: readonly SourceDeclarationEvidence[];\n readonly locator?: AssetEvidenceLocator;\n readonly locators?: readonly AssetEvidenceLocator[];\n readonly receipt?: CookReceipt;\n readonly receipts?: readonly CookReceipt[];\n readonly packageVerification?: PackageVerificationEvidence;\n readonly package?: AssetEvidencePackageInput;\n readonly artifacts?: Readonly<Record<string, ArtifactDescriptor>>;\n readonly artifactVerification?: Readonly<Record<string, ArtifactVerificationStatus>>;\n readonly runtime?: { readonly status: RuntimeEvidenceStatus };\n}\n\n/** Project the one public producer product into the shared evidence view. */\nexport function projectCookProductEvidence(\n product: import('./asset.js').CookProduct,\n locator?: AssetEvidenceLocator,\n): Result<AssetEvidence, AssetEvidenceError> {\n if (product.receipt.guid !== product.guid) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n product.guid,\n product.receipt.guid,\n product.guid,\n );\n }\n if (product.receipt.outputDigest !== product.digest) {\n return conflict(\n 'asset-evidence-digest-mismatch',\n product.guid,\n product.digest,\n product.receipt.outputDigest ?? 'missing receipt digest',\n );\n }\n return projectAssetEvidence({\n guid: product.guid,\n source: {\n origin: product.receipt.origin,\n inputFingerprint: product.receipt.inputFingerprint,\n },\n ...(locator === undefined ? {} : { locator }),\n receipt: product.receipt,\n package: {\n guid: product.guid,\n digest: product.digest,\n artifacts: Object.fromEntries(\n Object.entries(product.artifacts).map(([key, descriptor]) => [\n key,\n { descriptor, verification: 'passed' as const },\n ]),\n ),\n },\n });\n}\n\nexport { ASSET_EVIDENCE_ERROR_HINTS } from './asset-errors.js';\n\nfunction conflict(\n code: AssetEvidenceErrorCode,\n guid: string,\n observed: string,\n expected: string,\n): Result<never, AssetEvidenceError> {\n return err({\n code,\n expected,\n hint: ASSET_EVIDENCE_ERROR_HINTS[code],\n detail: { guid, observed, expected },\n });\n}\n\nfunction distinct<T>(values: readonly T[], key: (value: T) => string): readonly T[] {\n const result: T[] = [];\n const seen = new Set<string>();\n for (const value of values) {\n const identity = key(value);\n if (!seen.has(identity)) {\n seen.add(identity);\n result.push(value);\n }\n }\n return result;\n}\n\nfunction chooseSource(\n inputs: AssetEvidenceInputs,\n): Result<SourceDeclarationEvidence | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.source, ...(inputs.sources ?? [])].filter(\n (value): value is SourceDeclarationEvidence => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-source-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one source declaration per GUID',\n );\n }\n return ok(values[0]);\n}\n\nfunction chooseLocator(\n inputs: AssetEvidenceInputs,\n): Result<AssetEvidenceLocator | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.locator, ...(inputs.locators ?? [])].filter(\n (value): value is AssetEvidenceLocator => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-locator-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one package locator per GUID',\n );\n }\n return ok(values[0]);\n}\n\nfunction chooseReceipt(\n inputs: AssetEvidenceInputs,\n): Result<CookReceipt | undefined, AssetEvidenceError> {\n const values = distinct(\n [inputs.receipt, ...(inputs.receipts ?? [])].filter(\n (value): value is CookReceipt => value !== undefined,\n ),\n (value) => JSON.stringify(value),\n );\n if (values.length > 1) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n inputs.guid,\n JSON.stringify(values),\n 'one cook receipt per GUID',\n );\n }\n const receipt = values[0];\n if (receipt !== undefined && receipt.guid.toLowerCase() !== inputs.guid.toLowerCase()) {\n return conflict(\n 'asset-evidence-receipt-conflict',\n inputs.guid,\n receipt.guid,\n `receipt GUID ${inputs.guid}`,\n );\n }\n return ok(receipt);\n}\n\nfunction packageEvidence(inputs: AssetEvidenceInputs): PackageVerificationEvidence | undefined {\n if (inputs.packageVerification !== undefined) return inputs.packageVerification;\n if (inputs.package === undefined) return undefined;\n const statuses = Object.values(inputs.package.artifacts).map(\n (artifact) => artifact.verification ?? 'notChecked',\n );\n const status = statuses.some((value) => value === 'failed')\n ? 'failed'\n : statuses.length > 0 && statuses.every((value) => value === 'passed')\n ? 'passed'\n : 'notChecked';\n return {\n status,\n ...(inputs.package.digest !== undefined ? { digest: inputs.package.digest } : {}),\n };\n}\n\nfunction artifactsEvidence(\n inputs: AssetEvidenceInputs,\n): Readonly<Record<string, AssetEvidenceArtifact>> {\n const descriptors =\n inputs.artifacts ??\n Object.fromEntries(\n Object.entries(inputs.package?.artifacts ?? {}).map(([key, value]) => [\n key,\n value.descriptor,\n ]),\n );\n return Object.fromEntries(\n Object.entries(descriptors).map(([key, descriptor]) => [\n key,\n {\n descriptor,\n verification:\n inputs.artifactVerification?.[key] ??\n inputs.package?.artifacts[key]?.verification ??\n 'notChecked',\n },\n ]),\n );\n}\n\nfunction freshness(\n source: SourceDeclarationEvidence | undefined,\n receipt: CookReceipt | undefined,\n): CookFreshness {\n if (source?.origin === 'authoredPack') return 'notApplicable';\n if (receipt === undefined) return 'unknown';\n if (source?.inputFingerprint === undefined) return 'unknown';\n return source.inputFingerprint === receipt.inputFingerprint ? 'current' : 'stale';\n}\n\nfunction cookStatus(\n source: SourceDeclarationEvidence | undefined,\n receipt: CookReceipt | undefined,\n): CookStatus {\n if (source?.origin === 'authoredPack') return 'notRequired';\n if (receipt?.status === 'failed') return 'failed';\n if (receipt?.status === 'succeeded') return 'ready';\n if (source?.origin === 'sourceMeta') return 'notCooked';\n return 'unknown';\n}\n\n/** Join source, locator, receipt, package, artifact, and runtime facts into one view. */\nexport function projectAssetEvidence(\n inputs: AssetEvidenceInputs,\n): Result<AssetEvidence, AssetEvidenceError> {\n const sourceResult = chooseSource(inputs);\n if (!sourceResult.ok) return sourceResult;\n const locatorResult = chooseLocator(inputs);\n if (!locatorResult.ok) return locatorResult;\n const receiptResult = chooseReceipt(inputs);\n if (!receiptResult.ok) return receiptResult;\n\n const source = sourceResult.value;\n const locator = locatorResult.value;\n const receipt = receiptResult.value;\n const packageVerification = packageEvidence(inputs);\n if (\n receipt?.outputDigest !== undefined &&\n packageVerification?.digest !== undefined &&\n receipt.outputDigest !== packageVerification.digest\n ) {\n return conflict(\n 'asset-evidence-digest-mismatch',\n inputs.guid,\n packageVerification.digest,\n receipt.outputDigest,\n );\n }\n\n return ok({\n guid: inputs.guid,\n ...(locator?.packageUrl !== undefined ? { packageUrl: locator.packageUrl } : {}),\n ...(locator?.cookReceiptUrl !== undefined ? { cookReceiptUrl: locator.cookReceiptUrl } : {}),\n ...(source !== undefined ? { source } : {}),\n cook: {\n status: cookStatus(source, receipt),\n freshness: freshness(source, receipt),\n ...(receipt !== undefined ? { receipt } : {}),\n },\n ...(packageVerification !== undefined ? { package: packageVerification } : {}),\n artifacts: artifactsEvidence(inputs),\n runtime: { status: inputs.runtime?.status ?? 'unknown' },\n });\n}\n\nexport type {\n ArtifactVerificationStatus,\n CookFreshness,\n CookStatus,\n RuntimeEvidenceStatus,\n} from './asset.js';\nexport type { ArtifactDescriptor, ContentEncoding, Integrity };\n","// @forgeax/engine-types - Handle<T,M> brand + AssetTagMap + TagOf + 3 helpers SSOT.\n//\n// Single physical source-of-truth (feat-20260517-handle-type-unify M1 / D-2 / D-4 / D-7).\n// The package barrel `index.ts` re-exports this file; AI users import the\n// brand and helpers via `@forgeax/engine-types`, and IDE hover lands on this\n// file (charter F1 single-entry indexability).\n//\n// Contents (charter P4 consistent abstraction - 5 co-located building blocks):\n// - type Handle<T extends string, M extends 'unique' | 'shared'>\n// (double-axis phantom brand on top of `number`)\n// - type UniqueHandle<T> / SharedHandle<T> (mode-pinned aliases)\n// - interface AssetTagMap (14-member closed map mesh/texture/cube-texture/sampler/material/scene/audio/skin/skeleton/animation-clip/shader/font/render-pipeline/tileset/video)\n// - type TagOf<T extends Asset> (distributive conditional - 14+1 never tail)\n// - function toUnique<T>(raw) / toShared<T>(raw) (brand creation factories)\n// - function unwrapHandle<T,M>(h) (brand removal helper - cast inverse)\n//\n// The single `as Handle<T, M>` cast inside each factory is the brand-creation\n// structural cast (D-7 + AC-01 exemption); all other call sites must route\n// through these factories or `unwrapHandle` so that no `as unknown as Handle`\n// or `as unknown as number` literal survives anywhere outside this file\n// (AC-01 grep gate, M3 / M4 cleanup).\n//\n// Charter mapping: F1 (single-entry IDE autocomplete from\n// `@forgeax/engine-types`) + P3 (cross-mode rejection is a TS compile-time\n// failure red line) + P4 (consistent abstraction: brand + map + 3 factories\n// + 1 distributive conditional all co-located in this 1 file).\n\nimport type { Asset } from './index';\n\n/**\n * Phantom-branded Handle: a `number` carrying two type tags.\n *\n * @typeParam T - asset target tag (string literal, e.g. `'MeshAsset'`)\n * @typeParam M - release mode: `'unique'` (ECS-tracked via UniqueRefStore)\n * or `'shared'` (external owner, e.g. `AssetRegistry`)\n *\n * Runtime representation is a u32 number so the GPU upload path\n * (`GPUBuffer.writeBuffer(slot, ...)`) keeps zero-cost passthrough; only the\n * TS layer enforces non-assignability across modes / targets via the\n * `__handle` phantom field. The `__handle` field is type-only - runtime\n * objects never carry it (charter P4 zero-overhead abstraction).\n *\n * Cross-tag rejection: `Handle<'MeshAsset', M>` is not assignable to\n * `Handle<'TextureAsset', M>` and vice versa (the brand `target` field\n * differs).\n *\n * Cross-mode rejection: `Handle<T, 'unique'>` is not assignable to\n * `Handle<T, 'shared'>` and vice versa (the brand `mode` field differs);\n * this is the TS compile-time wall that prevents accidentally feeding a\n * unique-mode handle to a registry that owns its own release lifecycle (charter\n * P3 explicit failure red line; tests live in\n * `packages/types/src/__tests__/handle-brand.test-d.ts` and\n * `packages/ecs/src/__tests__/handle.test-d.ts`).\n *\n * AI users do not write `as Handle<...>` - handles come from registry\n * factories (`engine.assets.register<T>(asset).unwrap()` produces\n * `Handle<TagOf<T>, 'shared'>`; `world.uniqueRefs.alloc<T>(value)`\n * produces `Handle<T, 'unique'>` after M2). The only `as Handle` literal in the\n * codebase is the brand-creation cast inside `toUnique` / `toShared`\n * below (AC-01 exemption).\n */\nexport type Handle<T extends string, M extends 'unique' | 'shared'> = number & {\n readonly __handle: { readonly target: T; readonly mode: M };\n};\n\n/**\n * Convenience alias - unique-mode handle for asset target `T`.\n *\n * Intended for ECS-internal consumption (column slot read sites in the\n * unique-ref store, M2 rename). The `@forgeax/engine-ecs` barrel does NOT\n * re-export this alias name (AC-15 grep gate - keeps the AI-facing surface\n * narrow); callers outside ecs continue to write `Handle<T, 'unique'>`\n * literally.\n *\n * Schema vocab `'unique<T>'` derives the column field type to this alias via\n * `FieldValueType<T>` conditional inference (see\n * `packages/ecs/src/component.ts`).\n */\nexport type UniqueHandle<T extends string> = Handle<T, 'unique'>;\n\n/**\n * Convenience alias - shared-mode handle for asset target `T`.\n *\n * Mirrors `UniqueHandle<T>` for the refcounted-owner side; surfaces on\n * `AssetRegistry.register<T>` return signatures and `MeshFilter.assetHandle`\n * column type. Re-exported by the `@forgeax/engine-ecs` barrel (alongside\n * `Handle`) so AI users importing from ecs see the alias - this remains\n * subordinate to writing `Handle<T, 'shared'>` literally.\n *\n * Schema vocab `'shared<T>'` derives the column field type to this alias\n * via `FieldValueType<T>` conditional inference (feat-20260614 M5).\n */\nexport type SharedHandle<T extends string> = Handle<T, 'shared'>;\n\n/**\n * Asset.kind tag map - 13-member closed map keying each Asset variant\n * `kind` literal to its TS type name string literal (D-1 path (a)).\n *\n * Used by `TagOf<T>` distributive conditional below to derive the brand\n * `target` tag from an Asset variant TS type at register / inference time;\n * AI users adding a new Asset variant minor-add the corresponding\n * `kind -> 'XxxAsset'` row here so that `register<NewVariant>(asset)` returns\n * the correct `Handle<'XxxAsset', 'shared'>` automatically (this map is\n * the single must-edit point per Asset addition - charter F1 single-entry\n * indexability).\n *\n * The 13 members align byte-for-byte with the closed `Asset` union; adding\n * a new member to `Asset` without adding a row here surfaces as a\n * `TagOf<NewAsset>` resolving to `never` (charter P3 explicit failure -\n * downstream `register<NewAsset>` calls fail to compile).\n */\nexport interface AssetTagMap {\n mesh: 'MeshAsset';\n texture: 'TextureAsset';\n equirect: 'EquirectAsset';\n sampler: 'SamplerAsset';\n material: 'MaterialAsset';\n scene: 'SceneAsset';\n audio: 'AudioClipAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n skin: 'SkinAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n skeleton: 'SkeletonAsset';\n /** feat-20260523-skin-skeleton-animation M0 */\n 'animation-clip': 'AnimationClip';\n /** feat-20260713-animation-state-machine-plugin M2 / w13 */\n 'animation-graph': 'AnimationGraph';\n /** feat-20260528-material-shader-registration-unification M1 / w1 */\n /** feat-20260531-world-space-msdf-text-rendering M2 / w5 */\n font: 'FontAsset';\n /** feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend M1 / w5 */\n 'render-pipeline': 'RenderPipelineAsset';\n /** feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild */\n tileset: 'TilesetAsset';\n /** feat-20260623-world-space-video-asset M1 / w2 */\n video: 'VideoAsset';\n /** feat-20260728-wave1-vfx-contract-and-asset-cook M1 / m1-i1 */\n 'particle-effect': 'ParticleEffectAsset';\n}\n\n/**\n * Distributive conditional - maps an Asset variant TS type to its brand\n * `target` tag string literal (D-1 path (a)).\n *\n * `TagOf<MeshAsset>` resolves to `'MeshAsset'`; `TagOf<MaterialAsset>`\n * resolves to `'MaterialAsset'` even though `MaterialAsset` is itself the\n * pass-based single interface (MaterialAsset) - the\n * distributive conditional resolves to 'MaterialAsset' via kind: 'material'\n * collapse onto `'MaterialAsset'` because both share `kind: 'material'`\n * (research Finding 2).\n *\n * Asset variants without a matching `AssetTagMap` row (or a `kind` literal\n * outside the 5 closed values) resolve to `never`, surfacing the missing\n * row at every downstream `register<T>` consumer site (charter P3 explicit\n * failure).\n */\nexport type TagOf<T extends Asset> = T extends { kind: infer K }\n ? K extends keyof AssetTagMap\n ? AssetTagMap[K]\n : never\n : never;\n\n/**\n * Construct a `Handle<T, 'unique'>` from a raw u32. Brand-creation\n * structural cast - the `as Handle<T, 'unique'>` literal here is the\n * AC-01 exemption single point of brand creation (D-7); all other call\n * sites must route through this factory.\n *\n * Used by `World.allocUniqueRef<T>(value)` (M2 rename of allocUniqueRef)\n * to brand fresh handles that the ECS will track via the per-row release\n * loop. AI users typically\n * do not call this directly - it is the brand-creation primitive that the\n * ecs / runtime layers wrap.\n */\nexport function toUnique<T extends string>(raw: number): Handle<T, 'unique'> {\n return raw as Handle<T, 'unique'>;\n}\n\n/**\n * Construct a `Handle<T, 'shared'>` from a raw u32. Brand-creation\n * structural cast - the `as Handle<T, 'shared'>` literal here is the\n * AC-01 exemption single point of brand creation (D-7).\n *\n * Used by `AssetRegistry.register<T>(asset).unwrap()` to brand the returned handle\n * with `Handle<TagOf<T>, 'shared'>`, and by builtin handle constants\n * (`HANDLE_CUBE` / `HANDLE_TRIANGLE` / `HANDLE_ROOM_CUBE` / `BUILTIN_HANDLE_*`)\n * to brand compile-time u32 literals without caller-side `as unknown as`\n * casts (AC-05).\n */\nexport function toShared<T extends string>(raw: number): Handle<T, 'shared'> {\n return raw as Handle<T, 'shared'>;\n}\n\n/**\n * Remove the `Handle<T, M>` brand and recover the raw u32 carried inside.\n * Brand-removal helper - the inverse of `toUnique` / `toShared`.\n *\n * Runtime is identity (the brand `__handle` field is type-only; the\n * underlying number value is unchanged); the helper exists purely to\n * collapse all `as unknown as number` cast sites into a single function so\n * that AC-01 grep can surface stragglers (D-7 / D-8 cast collapse plan).\n *\n * Public on the types barrel (parallel to `toUnique` / `toShared`):\n * column read sites in unique-ref-store (M2 rename) / scene-instance-container,\n * AssetRegistry internal `Map<number, ...>` key reads, and any AI-user\n * code that needs to bridge a branded handle to a numeric ABI all call\n * this. AI users on the typical spawn-site / register-site surface\n * usually do not need it (charter P1 progressive disclosure — handle\n * stays branded end-to-end), but when a numeric escape is required this\n * is the single sanctioned escape.\n */\nexport function unwrapHandle<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return h;\n}\n\n/**\n * Slot boundary between the builtin tier and the user tier (feat-20260614 M6\n * D-15 / D-16). Builtin asset handles (the 5 process-static meshes:\n * HANDLE_CUBE=1 .. HANDLE_NINESLICE_QUAD=5) occupy slots `[1, BUILTIN_BASE)`;\n * user-tier handles minted by `World.sharedRefs.alloc` start at `BUILTIN_BASE`.\n *\n * Defined here in `@forgeax/engine-types` — the single dependency shared by\n * both `@forgeax/engine-ecs` (SharedRefStore `nextSlot` init + builtin-slot\n * fail-fast) and the package that authors process-static builtin payloads\n * dispatch + AssetRegistry index) — so the boundary is one named constant with\n * no cross-package circular dependency. Value 1024 is the historic\n * `FIRST_USER_HANDLE` literal, promoted to the shared SSOT.\n */\nexport const BUILTIN_BASE = 1024;\n\n// === Gen-slot codec SSOT (feat-20260623-asset-handle-generation M1 / w2) ==========\n//\n// Domain-agnostic pure bit operations — max-slot, max-gen, pack, unpack-slot,\n// unpack-gen, and the retire predicate (isRetiredSlot: gen > MAX_GEN, so gen\n// 255 is usable and a slot retires only when its bump would reach 256). This\n// is the single definition point\n// for the `(gen << 24) | slot` layout across entity and asset handles (D-1,\n// AC-15). Callers (ecs / runtime / ref stores) import from\n// @forgeax/engine-types; entity-side overflow throw and sentinel stay in ecs\n// (D-1). No domain concepts live here — just mask and shift.\n//\n// Bit layout (OOS-3: 32-bit number, 24-bit slot + 8-bit gen):\n// - slot: bits [0, 23], max value (1 << 24) - 1 = 16_777_215\n// - gen: bits [24, 31], max value 0xff = 255\n//\n// pack(slot, gen) fixed to (((gen & 0xff) << 24) | (slot & 0xffffff)) >>> 0\n// per D-7 hard constraint — the `>>> 0` prevents ToInt32 negative when\n// gen >= 128 (entity-handle.ts:73 comment documents this trap).\n\n/** Maximum slot index (2^24 - 1 = 16_777_215). */\nexport const MAX_SLOT = (1 << 24) - 1;\n\n/** Maximum generation value (2^8 - 1 = 255). */\nexport const MAX_GEN = 0xff;\n\n/**\n * Pack (slot, gen) into a u32 handle.\n *\n * The caller is responsible for ensuring slot does not exceed MAX_SLOT;\n * values above 24 bits are masked by `slot & 0xffffff`. Generation is\n * masked to 8 bits via `gen & 0xff`. The `>>> 0` forces unsigned u32\n * representation — without it, gen >= 128 produces a signed-negative\n * ToInt32 (D-7 hard constraint, entity-handle.ts:73).\n */\nexport function pack(slot: number, gen: number): number {\n return (((gen & 0xff) << 24) | (slot & 0xffffff)) >>> 0;\n}\n\n/** Extract the low 24 bits (slot) from a packed u32 handle. */\nexport function unpackSlot(v: number): number {\n return v & 0xffffff;\n}\n\n/** Extract the high 8 bits (generation) from a packed u32 handle. */\nexport function unpackGen(v: number): number {\n return (v >>> 24) & 0xff;\n}\n\n/**\n * Retire-when-gen-exceeds-MAX_GEN semantic: returns `true` when gen has\n * exceeded MAX_GEN (255), i.e. gen 255 is still a usable handle; a slot\n * retires only when its bumped generation reaches 256. A retired slot\n * never returns to the free list (AC-07).\n */\nexport function isRetiredSlot(gen: number): boolean {\n return gen > MAX_GEN;\n}\n\n// === Handle inspection helpers (feat-20260623-asset-handle-generation M1 / w2) ====\n//\n// Thin wrappers over the shared codec for Handle<T, M> consumers. handleSlot\n// and handleGeneration internally call unpackSlot / unpackGen — these are the\n// migration targets for unwrapHandle sites that only need the slot or gen\n// (decision q4). unwrapHandle stays as-is for sites that need the full\n// encoded value (D-5 excluded round-trip sites).\n\n/**\n * Extract the slot (low 24 bits) from a branded Handle.\n *\n * This is the runtime identity of the handle — the slot index that maps to\n * store payloads / GPU resource keys. Call sites that currently use\n * `unwrapHandle(h)` as a Map key should migrate here so the key stays stable\n * when gen > 0 (AC-09).\n */\nexport function handleSlot<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return unpackSlot(h as unknown as number);\n}\n\n/**\n * Extract the generation (high 8 bits) from a branded Handle.\n *\n * Used by store-level gen comparisons (resolve/retain/release) to detect\n * stale handles — the gen embedded during alloc is compared against the\n * store's current gen for the same slot.\n */\nexport function handleGeneration<T extends string, M extends 'unique' | 'shared'>(\n h: Handle<T, M>,\n): number {\n return unpackGen(h as unknown as number);\n}\n","import type { AssetKind } from '../asset-runtime.js';\nimport type { AssetGuid } from '../index.js';\nimport type { MaterialColorSpace } from './color-space.js';\n\nexport type MaterialParameterType =\n | 'bool'\n | 'f32'\n | 'i32'\n | 'u32'\n | 'vec2'\n | 'vec3'\n | 'vec4'\n | 'color'\n | 'texture';\n\nexport interface MaterialParameter {\n readonly name: string;\n readonly type: MaterialParameterType;\n /**\n * Asset-side transfer function for an authored color value. `color`\n * parameters default to `srgb`; numeric vectors default to linear unless\n * explicitly tagged. An asset-level `colorSpace` overrides this schema\n * default. Runtime material values are always linear.\n */\n readonly colorSpace?: MaterialColorSpace;\n readonly default?: MaterialValue;\n readonly optional?: boolean;\n readonly static?: boolean;\n}\n\nexport interface MaterialTextureCoordinates {\n readonly set?: number;\n /** Producer-owned logical-to-physical texture extent correction. */\n readonly physicalUvScale?: readonly [number, number];\n readonly transform?: {\n readonly offset?: readonly [number, number];\n readonly scale?: readonly [number, number];\n readonly rotation?: number;\n };\n}\n\nexport interface ResolvedMaterialTextureCoordinates {\n readonly set: number;\n readonly transform: {\n readonly offset: readonly [number, number];\n readonly scale: readonly [number, number];\n readonly rotation: number;\n };\n}\n\nexport function resolveMaterialTextureCoordinates(\n coordinates?: MaterialTextureCoordinates,\n): ResolvedMaterialTextureCoordinates {\n return {\n set: coordinates?.set ?? 0,\n transform: {\n offset: coordinates?.transform?.offset ?? [0, 0],\n scale: coordinates?.transform?.scale ?? [1, 1],\n rotation: coordinates?.transform?.rotation ?? 0,\n },\n };\n}\n\nexport type MaterialTextureReference = AssetGuid | number | string;\n\nexport interface MaterialTextureValue {\n readonly texture: MaterialTextureReference;\n readonly sampler?: MaterialTextureReference;\n readonly coordinates?: MaterialTextureCoordinates;\n readonly normalScale?: number;\n readonly occlusionStrength?: number;\n}\n\nexport type MaterialValue = boolean | number | readonly number[] | string | MaterialTextureValue;\n\nexport interface MaterialProgram {\n readonly module: string;\n readonly vertexEntry?: string;\n readonly fragmentEntry?: string;\n readonly moduleSlots?: Readonly<Record<string, string>>;\n}\n\nexport interface MaterialPass {\n readonly name: string;\n readonly program: MaterialProgram;\n readonly renderState?: Readonly<Record<string, unknown>>;\n}\n\nexport type MaterialPassList = readonly [MaterialPass, ...MaterialPass[]];\n\nexport interface MaterialAsset {\n readonly kind: 'material';\n /**\n * Transfer function override for all authored color parameters. Omitted\n * parameters ultimately default to sRGB.\n * Explicit `linear` is reserved for physical/imported data such as glTF\n * factors. Numeric values are never rewritten when this metadata changes.\n */\n readonly colorSpace?: MaterialColorSpace;\n readonly parent?: AssetGuid;\n readonly passes?: MaterialPassList;\n readonly parameters?: readonly MaterialParameter[];\n readonly values?: Readonly<Record<string, MaterialValue | null>>;\n}\n\nexport const materialAssetKind: AssetKind<MaterialAsset, 'material'> = {\n kind: 'material',\n} as AssetKind<MaterialAsset, 'material'>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Validate JSON-authored material descriptors at their loading boundary. */\nexport function assertMaterialAsset(\n value: unknown,\n context = 'material',\n): asserts value is MaterialAsset {\n if (!isRecord(value) || value.kind !== 'material') {\n throw new Error(`${context}: expected a material asset`);\n }\n if (\n value.colorSpace !== undefined &&\n value.colorSpace !== 'srgb' &&\n value.colorSpace !== 'linear'\n ) {\n throw new Error(`${context}: invalid colorSpace`);\n }\n if (value.passes !== undefined) {\n if (!Array.isArray(value.passes) || value.passes.length === 0) {\n throw new Error(`${context}: passes must be a non-empty array`);\n }\n for (const [index, pass] of value.passes.entries()) {\n if (!isRecord(pass) || typeof pass.name !== 'string' || !isRecord(pass.program)) {\n throw new Error(`${context}: pass ${index} is malformed`);\n }\n if (typeof pass.program.module !== 'string' || pass.program.module.length === 0) {\n throw new Error(`${context}: pass ${index} has no module identity`);\n }\n if (\n (pass.program.vertexEntry !== undefined && typeof pass.program.vertexEntry !== 'string') ||\n (pass.program.fragmentEntry !== undefined && typeof pass.program.fragmentEntry !== 'string')\n ) {\n throw new Error(`${context}: pass ${index} has malformed entry points`);\n }\n if (pass.program.moduleSlots !== undefined) {\n if (!isRecord(pass.program.moduleSlots)) {\n throw new Error(`${context}: pass ${index} has malformed module slots`);\n }\n for (const [name, slot] of Object.entries(pass.program.moduleSlots)) {\n if (typeof slot !== 'string')\n throw new Error(`${context}: module slot ${name} is not a string`);\n }\n }\n }\n }\n if (value.parameters !== undefined) {\n if (!Array.isArray(value.parameters))\n throw new Error(`${context}: parameters must be an array`);\n for (const [index, parameter] of value.parameters.entries()) {\n if (\n !isRecord(parameter) ||\n typeof parameter.name !== 'string' ||\n typeof parameter.type !== 'string'\n ) {\n throw new Error(`${context}: parameter ${index} is malformed`);\n }\n if (\n parameter.colorSpace !== undefined &&\n parameter.colorSpace !== 'srgb' &&\n parameter.colorSpace !== 'linear'\n ) {\n throw new Error(`${context}: parameter ${index} has invalid colorSpace`);\n }\n }\n }\n}\n","import type { MaterialValue } from './asset.js';\n\n/** Transfer function attached to an authored material color. */\nexport type MaterialColorSpace = 'srgb' | 'linear';\n\n/** Minimal schema shape needed to identify authored color values. */\nexport interface MaterialColorParameterSchema {\n readonly name: string;\n readonly type: string;\n readonly colorSpace?: MaterialColorSpace;\n}\n\n/** IEC 61966-2-1 sRGB electro-optical transfer function. */\nexport function srgbChannelToLinear(value: number): number {\n return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;\n}\n\n/** IEC 61966-2-1 inverse transfer function. */\nexport function linearChannelToSrgb(value: number): number {\n return value <= 0.0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - 0.055;\n}\n\n/**\n * Decode an authored RGB/RGBA value for linear runtime use.\n *\n * Only the first three color channels are transformed. Alpha and any\n * additional lanes are data and pass through unchanged.\n */\nexport function authoredColorToLinear(\n value: readonly number[],\n colorSpace: MaterialColorSpace = 'srgb',\n): number[] {\n if (colorSpace === 'linear') return [...value];\n return value.map((channel, index) => (index < 3 ? srgbChannelToLinear(channel) : channel));\n}\n\n/**\n * Resolve a material parameter's asset-side transfer-function contract.\n * `color` is an authored color and therefore defaults to sRGB. Numeric\n * vectors remain linear unless their schema explicitly marks them as colors.\n */\nexport function materialParameterColorSpace(\n parameter: MaterialColorParameterSchema,\n assetColorSpace?: MaterialColorSpace,\n): MaterialColorSpace | undefined {\n const isColor = parameter.type === 'color' || parameter.colorSpace !== undefined;\n if (!isColor) return undefined;\n return assetColorSpace ?? parameter.colorSpace ?? 'srgb';\n}\n\n/**\n * Project authored MaterialAsset values into a fresh runtime value map.\n * Asset values are never mutated, so repeated extraction cannot compound the\n * transfer function.\n */\nexport function materialValuesToLinearRuntime(\n values: Readonly<Record<string, MaterialValue | null>> | undefined,\n parameters: readonly MaterialColorParameterSchema[],\n assetColorSpace?: MaterialColorSpace,\n): Readonly<Record<string, MaterialValue | null>> {\n if (values === undefined) return {};\n const colorSpaces = new Map<string, MaterialColorSpace>();\n for (const parameter of parameters) {\n const colorSpace = materialParameterColorSpace(parameter, assetColorSpace);\n if (colorSpace !== undefined) colorSpaces.set(parameter.name, colorSpace);\n }\n\n const runtimeValues: Record<string, MaterialValue | null> = {};\n for (const [name, value] of Object.entries(values)) {\n const colorSpace = colorSpaces.get(name);\n runtimeValues[name] =\n colorSpace !== undefined && Array.isArray(value)\n ? authoredColorToLinear(value, colorSpace)\n : value;\n }\n return runtimeValues;\n}\n","export const MATERIAL_ERROR_CODES = [\n 'material-parent-not-found',\n 'material-circular-inheritance',\n 'material-no-effective-pass',\n 'material-value-unknown',\n 'material-value-type-mismatch',\n 'material-contract-program-mismatch',\n 'shader-module-id-missing',\n 'shader-module-id-duplicate',\n 'shader-module-not-found',\n 'shader-module-namespace-reserved',\n 'material-reflection-binding-mismatch',\n 'material-specialization-not-cooked',\n 'material-specialization-stale-generation',\n 'gltf-material-uv-set-missing',\n 'material-derived-interface-mismatch',\n 'material-texture-coordinate-invalid',\n 'material-payload-bounds',\n] as const;\n\nexport type MaterialErrorCode = (typeof MATERIAL_ERROR_CODES)[number];\n\nexport interface MaterialGenerationVector {\n readonly dependencies: Readonly<Record<string, number>>;\n}\n\nexport interface MaterialParentNotFoundDetail {\n readonly code: 'material-parent-not-found';\n readonly leaf: string;\n readonly missingParent: string;\n readonly chain: readonly string[];\n}\n\nexport interface MaterialCircularInheritanceDetail {\n readonly code: 'material-circular-inheritance';\n readonly leaf: string;\n readonly chain: readonly string[];\n}\n\nexport interface MaterialNoEffectivePassDetail {\n readonly code: 'material-no-effective-pass';\n readonly material: string;\n}\n\nexport interface MaterialValueUnknownDetail {\n readonly code: 'material-value-unknown';\n readonly material: string;\n readonly parameter: string;\n}\n\nexport interface MaterialValueTypeMismatchDetail {\n readonly code: 'material-value-type-mismatch';\n readonly material: string;\n readonly parameter: string;\n readonly expectedType: string;\n readonly actualType: string;\n}\n\nexport interface MaterialContractProgramMismatchDetail {\n readonly code: 'material-contract-program-mismatch';\n readonly material: string;\n readonly pass: string;\n readonly program: string;\n readonly expectedProgram: string;\n}\n\nexport interface ShaderModuleIdMissingDetail {\n readonly code: 'shader-module-id-missing';\n readonly source: string;\n}\n\nexport interface ShaderModuleIdDuplicateDetail {\n readonly code: 'shader-module-id-duplicate';\n readonly module: string;\n readonly sources: readonly string[];\n}\n\nexport interface ShaderModuleNotFoundDetail {\n readonly code: 'shader-module-not-found';\n readonly module: string;\n readonly source: string;\n}\n\nexport interface ShaderModuleNamespaceReservedDetail {\n readonly code: 'shader-module-namespace-reserved';\n readonly module: string;\n readonly namespace: string;\n}\n\nexport interface MaterialReflectionBindingMismatchDetail {\n readonly code: 'material-reflection-binding-mismatch';\n readonly material: string;\n readonly pass: string;\n readonly parameter: string;\n readonly expected: string;\n readonly actual: string;\n}\n\nexport interface MaterialSpecializationNotCookedDetail {\n readonly code: 'material-specialization-not-cooked';\n readonly material: string;\n readonly staticSelection: readonly string[];\n}\n\nexport interface MaterialSpecializationStaleGenerationDetail {\n readonly code: 'material-specialization-stale-generation';\n readonly material: string;\n readonly dependencies: readonly string[];\n readonly observed: MaterialGenerationVector;\n readonly current: MaterialGenerationVector;\n}\n\nexport interface GltfMaterialUvSetMissingDetail {\n readonly material: string;\n readonly primitive: string;\n readonly slot: string;\n readonly requestedSet: number;\n readonly availableSets: readonly number[];\n}\n\nexport interface MaterialDerivedInterfaceMismatchDetail {\n readonly code: 'material-derived-interface-mismatch';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly expectedIdentity?: string;\n readonly actualIdentity?: string;\n readonly parameter?: string;\n readonly action: 'recook';\n}\n\nexport interface MaterialTextureCoordinateInvalidDetail {\n readonly code: 'material-texture-coordinate-invalid';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly parameter: string;\n readonly slot: string;\n readonly reason: 'missing' | 'non-finite' | 'shape';\n readonly action: 'recook';\n}\n\nexport interface MaterialPayloadBoundsDetail {\n readonly code: 'material-payload-bounds';\n readonly stage: 'compile' | 'cook' | 'extract' | 'record';\n readonly material: string;\n readonly layoutIdentity: string;\n readonly slot: string;\n readonly byteOffset: number;\n readonly byteLength: number;\n readonly payloadBytes: number;\n readonly action: 'stop-draw';\n}\n\ninterface MaterialErrorDetailByCode {\n readonly 'material-parent-not-found': MaterialParentNotFoundDetail;\n readonly 'material-circular-inheritance': MaterialCircularInheritanceDetail;\n readonly 'material-no-effective-pass': MaterialNoEffectivePassDetail;\n readonly 'material-value-unknown': MaterialValueUnknownDetail;\n readonly 'material-value-type-mismatch': MaterialValueTypeMismatchDetail;\n readonly 'material-contract-program-mismatch': MaterialContractProgramMismatchDetail;\n readonly 'shader-module-id-missing': ShaderModuleIdMissingDetail;\n readonly 'shader-module-id-duplicate': ShaderModuleIdDuplicateDetail;\n readonly 'shader-module-not-found': ShaderModuleNotFoundDetail;\n readonly 'shader-module-namespace-reserved': ShaderModuleNamespaceReservedDetail;\n readonly 'material-reflection-binding-mismatch': MaterialReflectionBindingMismatchDetail;\n readonly 'material-specialization-not-cooked': MaterialSpecializationNotCookedDetail;\n readonly 'material-specialization-stale-generation': MaterialSpecializationStaleGenerationDetail;\n readonly 'gltf-material-uv-set-missing': GltfMaterialUvSetMissingDetail;\n readonly 'material-derived-interface-mismatch': MaterialDerivedInterfaceMismatchDetail;\n readonly 'material-texture-coordinate-invalid': MaterialTextureCoordinateInvalidDetail;\n readonly 'material-payload-bounds': MaterialPayloadBoundsDetail;\n}\n\nexport type MaterialErrorDetail = MaterialErrorDetailByCode[MaterialErrorCode];\n\nexport type MaterialErrorFor<C extends MaterialErrorCode> = {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: MaterialErrorDetailByCode[C];\n readonly message: string;\n};\n\nexport type MaterialError = {\n [C in MaterialErrorCode]: MaterialErrorFor<C>;\n}[MaterialErrorCode];\n\nconst MATERIAL_ERROR_POLICY = {\n 'material-parent-not-found': {\n expected: 'every parent GUID resolves to a MaterialAsset',\n hint: 'fix the parent GUID and resolve the material again',\n },\n 'material-circular-inheritance': {\n expected: 'the parent chain is acyclic',\n hint: 'remove the repeated GUID from the parent chain',\n },\n 'material-no-effective-pass': {\n expected: 'the resolved material has at least one pass',\n hint: 'add a pass to the root material or an inherited parent',\n },\n 'material-value-unknown': {\n expected: 'every value name is declared by the effective contract',\n hint: 'remove the value or declare the parameter in the root contract',\n },\n 'material-value-type-mismatch': {\n expected: 'each value matches its declared parameter type',\n hint: 'change the value to the declared parameter type',\n },\n 'material-contract-program-mismatch': {\n expected: 'the program satisfies the material contract',\n hint: 'align the program entries with the root contract',\n },\n 'shader-module-id-missing': {\n expected: 'each WGSL source declares a module ID',\n hint: 'add a compiler-native module ID declaration to the WGSL source',\n },\n 'shader-module-id-duplicate': {\n expected: 'each module ID has one source provenance',\n hint: 'rename one module or remove the duplicate source',\n },\n 'shader-module-not-found': {\n expected: 'every referenced module exists in the source catalog',\n hint: 'add the module to the source catalog or fix the reference',\n },\n 'shader-module-namespace-reserved': {\n expected: 'user modules use a non-reserved namespace',\n hint: 'choose a module ID outside the reserved namespace',\n },\n 'material-reflection-binding-mismatch': {\n expected: 'reflection matches the material contract bindings',\n hint: 'update the contract or WGSL binding and cook again',\n },\n 'material-specialization-not-cooked': {\n expected: 'the requested specialization has a cooked artifact',\n hint: 'run the build or development cook path for this selection',\n },\n 'material-specialization-stale-generation': {\n expected: 'all specialization dependencies share one generation',\n hint: 'retry after dependent assets and sources settle',\n },\n 'gltf-material-uv-set-missing': {\n expected: 'each texture slot references an available primitive UV set',\n hint: 'add the requested UV set to the primitive and re-import it',\n },\n 'material-derived-interface-mismatch': {\n expected: 'the generated material interface matches the derived schema interface',\n hint: 'repair the schema or WGSL producer and recook the material',\n },\n 'material-texture-coordinate-invalid': {\n expected: 'every texture coordinate record is finite and complete',\n hint: 'repair the texture metadata or coordinates and recook the material',\n },\n 'material-payload-bounds': {\n expected: 'every material payload write stays within the derived payload',\n hint: 'repair the derived payload owner before submitting the draw',\n },\n} satisfies {\n readonly [C in MaterialErrorCode]: {\n readonly expected: string;\n readonly hint: string;\n };\n};\n\nexport const MATERIAL_ERROR_EXPECTED: Readonly<Record<MaterialErrorCode, string>> =\n Object.fromEntries(\n MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].expected]),\n ) as Readonly<Record<MaterialErrorCode, string>>;\n\nexport const MATERIAL_ERROR_HINTS: Readonly<Record<MaterialErrorCode, string>> = Object.fromEntries(\n MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].hint]),\n) as Readonly<Record<MaterialErrorCode, string>>;\n\nexport function createMaterialError<C extends MaterialErrorCode>(\n code: C,\n detail: MaterialErrorDetailByCode[C],\n message = `${code}: ${MATERIAL_ERROR_EXPECTED[code]}`,\n): MaterialErrorFor<C> {\n return {\n code,\n expected: MATERIAL_ERROR_EXPECTED[code],\n hint: MATERIAL_ERROR_HINTS[code],\n detail,\n message,\n };\n}\n","import { err, ok, type Result } from '../result.js';\nimport type { MaterialAsset, MaterialParameter, MaterialPass, MaterialValue } from './asset.js';\nimport type { MaterialError } from './errors.js';\nimport { createMaterialError } from './errors.js';\n\nexport type MaterialTable = Readonly<Record<string, MaterialAsset>>;\n\nexport interface ResolvedMaterial {\n readonly leaf: string;\n readonly chain: readonly string[];\n readonly asset: MaterialAsset;\n}\n\nexport function materialGuidText(value: string | Uint8Array): string {\n return typeof value === 'string'\n ? value\n : Array.from(value, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nfunction valueType(value: MaterialValue): string {\n if (typeof value === 'boolean') return 'bool';\n if (typeof value === 'number') return 'number';\n if (typeof value === 'string') return 'string';\n if (Array.isArray(value)) return `vec${value.length}`;\n return 'texture';\n}\n\nfunction parameterTypeMatches(parameter: MaterialParameter, value: MaterialValue): boolean {\n switch (parameter.type) {\n case 'bool':\n return typeof value === 'boolean';\n case 'f32':\n case 'i32':\n case 'u32':\n return typeof value === 'number';\n case 'vec2':\n return Array.isArray(value) && value.length === 2;\n case 'vec3':\n return Array.isArray(value) && value.length === 3;\n case 'vec4':\n case 'color':\n return Array.isArray(value) && value.length === 4;\n case 'texture':\n // Runtime asset handles are branded numbers at the type level. The\n // brand is erased before a material reaches the resolver, so numeric\n // handles must remain valid texture values alongside structured\n // texture descriptors.\n return (\n typeof value === 'string' ||\n (typeof value === 'number' && Number.isInteger(value) && value >= 0) ||\n (typeof value === 'object' && !Array.isArray(value))\n );\n }\n}\n\nfunction validateValues(\n material: string,\n values: Readonly<Record<string, MaterialValue | null>>,\n parameters: readonly MaterialParameter[] | undefined,\n): Result<true, MaterialError> {\n if (parameters === undefined) return ok(true);\n const declarations = new Map(parameters.map((parameter) => [parameter.name, parameter]));\n for (const [name, value] of Object.entries(values)) {\n const parameter = declarations.get(name);\n if (parameter === undefined) {\n return err(\n createMaterialError('material-value-unknown', {\n code: 'material-value-unknown',\n material,\n parameter: name,\n }),\n );\n }\n if (value === null) {\n if (!parameter.optional) {\n return err(\n createMaterialError('material-value-type-mismatch', {\n code: 'material-value-type-mismatch',\n material,\n parameter: name,\n expectedType: parameter.type,\n actualType: 'null',\n }),\n );\n }\n continue;\n }\n if (!parameterTypeMatches(parameter, value)) {\n return err(\n createMaterialError('material-value-type-mismatch', {\n code: 'material-value-type-mismatch',\n material,\n parameter: name,\n expectedType: parameter.type,\n actualType: valueType(value),\n }),\n );\n }\n }\n return ok(true);\n}\n\nfunction mergePasses(\n inherited: readonly MaterialPass[] | undefined,\n override: readonly MaterialPass[] | undefined,\n): readonly MaterialPass[] | undefined {\n if (inherited === undefined && override === undefined) return undefined;\n const passes = [...(inherited ?? [])];\n for (const next of override ?? []) {\n const index = passes.findIndex((current) => current.name === next.name);\n if (index === -1) passes.push(next);\n else passes[index] = next;\n }\n return passes;\n}\n\nfunction mergeMaterial(parent: MaterialAsset, child: MaterialAsset): MaterialAsset {\n const values: Record<string, MaterialValue> = {};\n for (const [name, value] of Object.entries(parent.values ?? {})) {\n if (value !== null) values[name] = value;\n }\n for (const [name, value] of Object.entries(child.values ?? {})) {\n if (value === null) delete values[name];\n else values[name] = value;\n }\n const passes = mergePasses(parent.passes, child.passes);\n return {\n kind: 'material',\n ...((child.colorSpace ?? parent.colorSpace) !== undefined\n ? { colorSpace: child.colorSpace ?? parent.colorSpace }\n : {}),\n ...(passes !== undefined && passes.length > 0\n ? { passes: passes as NonNullable<MaterialAsset['passes']> }\n : {}),\n ...(parent.parameters !== undefined ? { parameters: parent.parameters } : {}),\n ...(Object.keys(values).length > 0 ? { values } : {}),\n };\n}\n\nfunction resolveChain(\n id: string,\n leaf: string,\n table: MaterialTable,\n stack: readonly string[],\n): Result<ResolvedMaterial, MaterialError> {\n if (stack.includes(id)) {\n return err(\n createMaterialError('material-circular-inheritance', {\n code: 'material-circular-inheritance',\n leaf,\n chain: [...stack, id],\n }),\n );\n }\n const current = table[id];\n if (current === undefined) {\n return err(\n createMaterialError('material-parent-not-found', {\n code: 'material-parent-not-found',\n leaf,\n missingParent: id,\n chain: [...stack, id],\n }),\n );\n }\n const parent = current.parent === undefined ? undefined : materialGuidText(current.parent);\n if (parent === undefined) {\n if (current.passes === undefined || current.passes.length === 0) {\n return err(\n createMaterialError('material-no-effective-pass', {\n code: 'material-no-effective-pass',\n material: leaf,\n }),\n );\n }\n const values: Record<string, MaterialValue> = {};\n for (const [name, value] of Object.entries(current.values ?? {})) {\n if (value !== null) values[name] = value;\n }\n const valid = validateValues(id, values, current.parameters);\n if (!valid.ok) return valid;\n return ok({ leaf, chain: [id], asset: { ...current, values } });\n }\n\n const parentResult = resolveChain(parent, leaf, table, [...stack, id]);\n if (!parentResult.ok) return parentResult;\n const valid = validateValues(id, current.values ?? {}, parentResult.value.asset.parameters);\n if (!valid.ok) return valid;\n const merged = mergeMaterial(parentResult.value.asset, current);\n if (merged.passes === undefined || merged.passes.length === 0) {\n return err(\n createMaterialError('material-no-effective-pass', {\n code: 'material-no-effective-pass',\n material: leaf,\n }),\n );\n }\n return ok({ leaf, chain: [...parentResult.value.chain, id], asset: merged });\n}\n\nexport function resolveMaterialAsset(\n leaf: string,\n table: MaterialTable,\n): Result<ResolvedMaterial, MaterialError> {\n return resolveChain(leaf, leaf, table, []);\n}\n","import type { CatalogDiagnostic } from './asset-producer.js';\nimport type { CatalogEntry } from './catalog.js';\n\n/** Versioned wire shape shared by the asset producer and browser consumers. */\nexport const RUNTIME_ASSET_BINDING_SCHEMA = 'runtime-asset-binding-v1' as const;\nexport const RUNTIME_CATALOG_SNAPSHOT_SCHEMA = 'runtime-catalog-snapshot-v1' as const;\n\nexport type RuntimeScopeStatus = 'unbound' | 'transitioning' | 'ready' | 'degraded' | 'unavailable';\n\n/**\n * Logical projection of one package-declared asset root into the runtime\n * catalog coordinate space. This carries no filesystem path across the wire.\n */\nexport interface RuntimeCatalogRoot {\n readonly root: string;\n readonly catalogPrefix: string;\n}\n\n/**\n * The only identity a browser-side asset consumer may use for a dev realm.\n * `scopeId` describes ownership; `generation` rejects stale browser work.\n * Filesystem roots intentionally do not cross this wire contract; catalogRoots\n * is only the logical declaration-to-catalog projection used by browser views.\n */\nexport interface RuntimeAssetBinding {\n readonly schemaVersion: typeof RUNTIME_ASSET_BINDING_SCHEMA;\n readonly gameId: string;\n readonly scopeId: string;\n readonly generation: number;\n readonly status: RuntimeScopeStatus;\n readonly catalogUrl: string;\n readonly importUrlBase: string;\n readonly packageUrlBase: string;\n readonly catalogRoots?: readonly RuntimeCatalogRoot[];\n readonly authority?: 'authoritative' | 'degraded';\n readonly diagnostics?: readonly CatalogDiagnostic[];\n}\n\nexport function isRuntimeCatalogRoots(value: unknown): value is readonly RuntimeCatalogRoot[] {\n return (\n Array.isArray(value) &&\n value.every(\n (root) =>\n root !== null &&\n typeof root === 'object' &&\n typeof (root as { root?: unknown }).root === 'string' &&\n typeof (root as { catalogPrefix?: unknown }).catalogPrefix === 'string',\n )\n );\n}\n\n/** Authority-bearing catalog response for one runtime scope. */\nexport interface RuntimeCatalogSnapshot {\n readonly schemaVersion: typeof RUNTIME_CATALOG_SNAPSHOT_SCHEMA;\n readonly scopeId: string;\n readonly generation: number;\n readonly authority: 'authoritative' | 'degraded';\n readonly entries: readonly CatalogEntry[];\n readonly diagnostics: readonly CatalogDiagnostic[];\n}\n\n/**\n * Make a route below the engine's scoped runtime namespace. The helper is\n * deliberately pure so hosts and tests cannot drift on URL construction.\n */\nexport function runtimeScopePath(\n binding: Pick<RuntimeAssetBinding, 'scopeId' | 'generation'>,\n suffix = '',\n): string {\n const normalized = suffix.length === 0 ? '' : `/${suffix.replace(/^\\/+/, '')}`;\n return `/__pack/scopes/${encodeURIComponent(binding.scopeId)}/${binding.generation}${normalized}`;\n}\n\n/**\n * Create the fixed binding used by a standalone Vite game host.\n *\n * A standalone host still has one explicit realm: its game owns the roots and\n * the browser endpoints are derived from the same scope/generation pair. The\n * optional scope override is only for a host-level test server that mounts\n * several standalone entrypoints behind one deliberately shared test realm;\n * production hosts should leave it unset.\n */\nexport function createStandaloneRuntimeAssetBinding(\n gameId: string,\n scopeId = gameId,\n basePath = '',\n): RuntimeAssetBinding {\n const normalizedBase = basePath === '/' ? '' : basePath.replace(/\\/+$/, '');\n const identity = { scopeId, generation: 1 } as const;\n const scopedPath = runtimeScopePath(identity);\n const hostPrefix = normalizedBase.startsWith('/') ? normalizedBase : `/${normalizedBase}`;\n const prefix = normalizedBase.length === 0 ? '' : hostPrefix;\n return {\n schemaVersion: RUNTIME_ASSET_BINDING_SCHEMA,\n gameId,\n scopeId,\n generation: identity.generation,\n status: 'ready',\n catalogUrl: `${prefix}${scopedPath}/catalog.json`,\n importUrlBase: `${prefix}${scopedPath}/import`,\n packageUrlBase: prefix,\n };\n}\n\nexport function runtimeScopeMatches(\n binding: Pick<RuntimeAssetBinding, 'scopeId' | 'generation'> | undefined,\n scopeId: string,\n generation: number,\n): boolean {\n return binding?.scopeId === scopeId && binding.generation === generation;\n}\n","// derive(schema) — paramSchema -> one DerivedMaterialInterface.\n// feat-20260613-material-paramschema-driven-binding M1 / w3\n//\n// Decision anchors (plan-strategy §2):\n// - D-2 single pure function, no side effect; one signature consumed by 3\n// downstream paths (BGL build / UBO record / loader-extract).\n// - D-3 consecutive numeric entries are run-merged into one UBO entry at\n// one binding slot (uniform buffer); std140-aligned offsets.\n// - D-4 every texture* family entry auto-pairs a filtering sampler at\n// binding-1 (sampler emitted FIRST, then texture); sampler /\n// sampler_comparison stay user-declared.\n// - D-7 type set is the 14-literal MaterialParamType union.\n// - D-12 empty schema is graceful: bglEntries=[] / totalBytes=0 / fields empty.\n//\n// std140 alignment table (WGSL uniform):\n// - f32 / i32 / u32 size 4 align 4\n// - vec2<f32> size 8 align 8\n// - vec3<f32> size 12 align 16\n// - vec4 / color (rgba) size 16 align 16\n// - struct round-up: totalBytes is rounded up to 16-byte alignment.\n\nimport type {\n BindGroupLayoutEntry,\n MaterialParamType,\n NumericParamType,\n ParamSchemaEntry,\n TextureBindingParamType,\n} from './index.js';\n\nconst FRAGMENT = 0x2 as GPUShaderStageFlags;\n\nconst NUMERIC_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'f32',\n 'i32',\n 'u32',\n 'vec2',\n 'vec3',\n 'vec4',\n 'color',\n]);\n\nconst TEXTURE_VIEW_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'texture2d',\n 'texture_cube',\n 'texture_depth_2d',\n 'texture_cube_array',\n]);\n\nconst SAMPLER_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n 'sampler',\n 'sampler_comparison',\n]);\n\nconst ALL_TYPES: ReadonlySet<MaterialParamType> = new Set<MaterialParamType>([\n ...NUMERIC_TYPES,\n ...TEXTURE_VIEW_TYPES,\n ...SAMPLER_TYPES,\n 'storage_buffer',\n]);\n\ninterface NumericFootprint {\n readonly size: number;\n readonly align: number;\n}\n\nfunction numericFootprint(t: NumericParamType): NumericFootprint {\n switch (t) {\n case 'f32':\n case 'i32':\n case 'u32':\n return { size: 4, align: 4 };\n case 'vec2':\n return { size: 8, align: 8 };\n case 'vec3':\n return { size: 12, align: 16 };\n case 'vec4':\n case 'color':\n return { size: 16, align: 16 };\n }\n}\n\nfunction alignUp(value: number, alignment: number): number {\n return (value + alignment - 1) & ~(alignment - 1);\n}\n\ninterface TextureBglDescriptor {\n readonly sampleType: GPUTextureSampleType;\n readonly viewDimension: GPUTextureViewDimension;\n}\n\nfunction textureBglDescriptor(t: TextureBindingParamType): TextureBglDescriptor {\n switch (t) {\n case 'texture2d':\n return { sampleType: 'float', viewDimension: '2d' };\n case 'texture_cube':\n return { sampleType: 'float', viewDimension: 'cube' };\n case 'texture_depth_2d':\n return { sampleType: 'depth', viewDimension: '2d' };\n case 'texture_cube_array':\n return { sampleType: 'float', viewDimension: 'cube-array' };\n case 'sampler':\n case 'sampler_comparison':\n // Not a texture view — caller must dispatch separately. Falling through\n // here is a defensive guard; numericFootprint / sampler handler covers\n // these branches before this function is reached.\n throw new Error(`derive: textureBglDescriptor called on sampler-family type '${t}'`);\n }\n}\n\nfunction samplerBindingType(t: 'sampler' | 'sampler_comparison'): GPUSamplerBindingType {\n return t === 'sampler' ? 'filtering' : 'comparison';\n}\n\n/** Single UBO sub-entry — one merged std140 slot. */\nexport interface UboFieldLayout {\n readonly name: string;\n readonly offset: number;\n readonly size: number;\n readonly type: NumericParamType;\n}\n\nexport interface DerivedNumericMember extends UboFieldLayout {\n readonly alignment: number;\n}\n\nexport interface MaterialCoordinateRecordLayout {\n readonly parameter: string;\n readonly offset: number;\n readonly size: 32;\n readonly alignment: 16;\n readonly transformMember: string;\n readonly metadataMember: string;\n}\n\nexport type MaterialResourceKind = 'sampler' | 'texture' | 'storage-buffer';\n\nexport interface MaterialResourceBindingLayout {\n readonly name: string;\n readonly parameter?: string;\n readonly kind: MaterialResourceKind;\n readonly binding: number;\n}\n\nexport interface MaterialParameterResourceProjection {\n readonly kind: 'sampler' | 'storage-buffer';\n readonly name: string;\n readonly type: 'sampler' | 'sampler_comparison' | 'storage_buffer';\n readonly resource: MaterialResourceBindingLayout;\n readonly coordinates?: never;\n}\n\nexport interface MaterialParameterTextureProjection {\n readonly kind: 'texture';\n readonly name: string;\n readonly type: TextureBindingParamType;\n readonly coordinates: MaterialCoordinateRecordLayout;\n readonly resource: {\n readonly parameter: string;\n readonly texture: MaterialResourceBindingLayout;\n readonly sampler: MaterialResourceBindingLayout;\n };\n}\n\nexport interface MaterialParameterNumericProjection {\n readonly kind: 'numeric';\n readonly name: string;\n readonly type: NumericParamType;\n readonly member: DerivedNumericMember;\n readonly coordinates?: never;\n readonly resource?: never;\n}\n\nexport type MaterialParameterProjection =\n | MaterialParameterNumericProjection\n | MaterialParameterTextureProjection\n | MaterialParameterResourceProjection;\n\nexport interface DerivedMaterialInterface {\n readonly bglEntries: readonly BindGroupLayoutEntry[];\n readonly uboLayout: UboLayout;\n readonly numericMembers: readonly DerivedNumericMember[];\n readonly coordinateRecords: readonly MaterialCoordinateRecordLayout[];\n readonly resourceBindings: readonly MaterialResourceBindingLayout[];\n readonly totalBytes: number;\n readonly layoutIdentity: string;\n readonly textureFieldNames: ReadonlySet<string>;\n readonly samplerForTexture: ReadonlyMap<string, string>;\n readonly userRegionBindingEnd: number;\n}\n\nexport interface UboLayout {\n readonly entries: readonly UboFieldLayout[];\n readonly totalBytes: number;\n}\n\nexport type DeriveOutput = DerivedMaterialInterface;\n\nexport interface ImmutableParamSchemaProjection {\n readonly ownerId: string;\n readonly revision: number;\n readonly schema: readonly ParamSchemaEntry[];\n readonly derivedInterface: DerivedMaterialInterface;\n}\n\nexport interface ParamSchemaProjectionOwnerStats {\n readonly admissions: number;\n readonly derivations: number;\n readonly projections: number;\n}\n\nexport type ParamSchemaDeriveObservationKind =\n | 'admitted-identity-hit'\n | 'unregistered-fallback-derive'\n | 'fallback-layout-sha';\n\nexport interface ParamSchemaDeriveObserver {\n readonly enabled: boolean;\n readonly observe: (event: {\n readonly kind: ParamSchemaDeriveObservationKind;\n readonly site: string;\n }) => void;\n}\n\ninterface OwnedParamSchemaProjection extends ImmutableParamSchemaProjection {\n readonly canonicalSchema: string;\n}\n\n// Runtime consumers can still receive the schema array through older API\n// surfaces. Admission binds that immutable array identity back to the one\n// owner projection, so those compatibility calls return the admitted result\n// without deriving or hashing again. Unregistered schemas keep the pure\n// offline/compiler behavior.\nconst ADMITTED_PARAM_SCHEMA_PROJECTIONS = new WeakMap<\n readonly ParamSchemaEntry[],\n DerivedMaterialInterface\n>();\n\n/**\n * Immutable/revision owner for runtime ParamSchema projections.\n *\n * A new `(ownerId, revision)` performs exactly one pure derivation. Repeating\n * the same admission is idempotent when the schema bytes match and fails loud\n * when a revision is reused for different schema content. The admitted schema\n * is cloned and frozen so caller-side mutation cannot alter a published\n * revision.\n */\nexport class ParamSchemaProjectionOwner {\n readonly #projections = new Map<string, OwnedParamSchemaProjection>();\n #admissions = 0;\n #derivations = 0;\n\n admit(args: {\n readonly ownerId: string;\n readonly revision: number;\n readonly schema: readonly ParamSchemaEntry[];\n }): ImmutableParamSchemaProjection {\n if (args.ownerId.length === 0) {\n throw new Error('ParamSchemaProjectionOwner: ownerId must be non-empty');\n }\n if (!Number.isSafeInteger(args.revision) || args.revision < 1) {\n throw new Error('ParamSchemaProjectionOwner: revision must be a positive safe integer');\n }\n this.#admissions += 1;\n const key = `${args.ownerId}\\u0000${args.revision}`;\n const canonicalSchema = JSON.stringify(args.schema);\n const existing = this.#projections.get(key);\n if (existing !== undefined) {\n if (existing.canonicalSchema !== canonicalSchema) {\n throw new Error(\n `ParamSchemaProjectionOwner: ${args.ownerId}@${args.revision} reused with different schema content`,\n );\n }\n return existing;\n }\n\n const schema = freezeParamSchema(args.schema);\n const derivedInterface = freezeDerivedMaterialInterface(derivePure(schema));\n this.#derivations += 1;\n ADMITTED_PARAM_SCHEMA_PROJECTIONS.set(schema, derivedInterface);\n const projection = Object.freeze({\n ownerId: args.ownerId,\n revision: args.revision,\n schema,\n derivedInterface,\n canonicalSchema,\n });\n this.#projections.set(key, projection);\n return projection;\n }\n\n get(ownerId: string, revision: number): ImmutableParamSchemaProjection | undefined {\n return this.#projections.get(`${ownerId}\\u0000${revision}`);\n }\n\n stats(): ParamSchemaProjectionOwnerStats {\n return Object.freeze({\n admissions: this.#admissions,\n derivations: this.#derivations,\n projections: this.#projections.size,\n });\n }\n}\n\n/**\n * Pure derivation: paramSchema -> BGL entries + UBO byte layout + field maps.\n *\n * The function has no side effects and is the SSOT for BGL / UBO / loader\n * lookup tables (D-2). Runtime / vite-plugin-shader / loader all call into\n * this single entry point; in particular, `userRegionBindingEnd` is the\n * post-user-region binding index that engine-injected groups (shadow / IBL\n * / lightmap, see D-6) must start from.\n *\n * Throws on schema authoring errors:\n * - duplicate entry name (numeric or non-numeric)\n * - unrecognised type literal (not in the 14-member union)\n * - empty entry name\n * - user-declared name collides with the auto-paired `<tex>_sampler`\n */\nexport function derive(schema: readonly ParamSchemaEntry[]): DeriveOutput {\n const admitted = ADMITTED_PARAM_SCHEMA_PROJECTIONS.get(schema);\n if (admitted !== undefined) return admitted;\n return derivePure(schema);\n}\n\n/**\n * Debug-only observation seam for runtime compatibility consumers.\n *\n * This wrapper deliberately does not cache or change derivation semantics. It\n * only records whether the supplied schema identity was admitted by the\n * ParamSchemaProjectionOwner before delegating to the existing pure `derive`\n * function. Keeping the observer out of the normal `derive` signature leaves\n * production callers on the existing zero-argument path.\n */\nexport function deriveObserved(\n schema: readonly ParamSchemaEntry[],\n observer: ParamSchemaDeriveObserver,\n site: string,\n): DeriveOutput {\n const admitted = ADMITTED_PARAM_SCHEMA_PROJECTIONS.has(schema);\n const output = derive(schema);\n if (observer.enabled) {\n observer.observe({\n kind: admitted ? 'admitted-identity-hit' : 'unregistered-fallback-derive',\n site,\n });\n if (!admitted) observer.observe({ kind: 'fallback-layout-sha', site });\n }\n return output;\n}\n\nfunction derivePure(schema: readonly ParamSchemaEntry[]): DeriveOutput {\n const bglEntries: BindGroupLayoutEntry[] = [];\n const uboFields: UboFieldLayout[] = [];\n const numericMembers: DerivedNumericMember[] = [];\n const coordinateRecords: MaterialCoordinateRecordLayout[] = [];\n const resourceBindings: MaterialResourceBindingLayout[] = [];\n const textureFieldNames = new Set<string>();\n const samplerForTexture = new Map<string, string>();\n const seenNames = new Set<string>();\n const reservedSamplerNames = new Set<string>();\n\n let nextBinding = 0;\n let uboCursor = 0;\n let uboBinding: number | null = null;\n\n const ensureUboBinding = (): number => {\n if (uboBinding !== null) return uboBinding;\n uboBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: uboBinding,\n visibility: FRAGMENT,\n buffer: { type: 'uniform' },\n });\n return uboBinding;\n };\n\n for (const rawEntry of schema) {\n const entry = rawEntry as ParamSchemaEntry;\n if (entry.name.length === 0) {\n throw new Error('derive: schema entry name must be non-empty');\n }\n if (!ALL_TYPES.has(entry.type)) {\n throw new Error(`derive: unrecognised paramSchema type literal '${entry.type}'`);\n }\n if (seenNames.has(entry.name)) {\n throw new Error(`derive: duplicate paramSchema entry name '${entry.name}'`);\n }\n if (reservedSamplerNames.has(entry.name)) {\n throw new Error(\n `derive: paramSchema entry '${entry.name}' collides with auto-paired sampler name`,\n );\n }\n seenNames.add(entry.name);\n\n if (NUMERIC_TYPES.has(entry.type)) {\n const numericType = entry.type as NumericParamType;\n const { size, align } = numericFootprint(numericType);\n ensureUboBinding();\n const offset = alignUp(uboCursor, align);\n uboFields.push({ name: entry.name, offset, size, type: numericType });\n numericMembers.push({ name: entry.name, offset, size, alignment: align, type: numericType });\n uboCursor = offset + size;\n continue;\n }\n\n if (TEXTURE_VIEW_TYPES.has(entry.type)) {\n const texType = entry.type as TextureBindingParamType;\n ensureUboBinding();\n const coordinateOffset = alignUp(uboCursor, 16);\n const coordinates: MaterialCoordinateRecordLayout = {\n parameter: entry.name,\n offset: coordinateOffset,\n size: 32,\n alignment: 16,\n transformMember: `${entry.name}CoordinatesTransform`,\n metadataMember: `${entry.name}CoordinatesMetadata`,\n };\n coordinateRecords.push(coordinates);\n uboCursor = coordinateOffset + coordinates.size;\n const { sampleType, viewDimension } = textureBglDescriptor(texType);\n // Sampler-first per plan §D-4: emit auto-paired filtering sampler at\n // binding N, then the texture view at binding N+1. Matches the actual\n // WGSL @binding declaration order in the 5 built-in shaders (sampler\n // declared on the odd binding, texture on the even+1 binding).\n const samplerName = `${entry.name}_sampler`;\n if (seenNames.has(samplerName)) {\n throw new Error(\n `derive: auto-paired sampler name '${samplerName}' collides with existing entry`,\n );\n }\n reservedSamplerNames.add(samplerName);\n samplerForTexture.set(entry.name, samplerName);\n const samplerBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: samplerBinding,\n visibility: FRAGMENT,\n sampler: { type: 'filtering' },\n });\n resourceBindings.push({\n name: samplerName,\n parameter: entry.name,\n kind: 'sampler',\n binding: samplerBinding,\n });\n\n const texBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: texBinding,\n visibility: FRAGMENT,\n texture: { sampleType, viewDimension, multisampled: false },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'texture',\n binding: texBinding,\n });\n textureFieldNames.add(entry.name);\n continue;\n }\n\n if (SAMPLER_TYPES.has(entry.type)) {\n const samplerType = entry.type as 'sampler' | 'sampler_comparison';\n const samplerBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: samplerBinding,\n visibility: FRAGMENT,\n sampler: { type: samplerBindingType(samplerType) },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'sampler',\n binding: samplerBinding,\n });\n continue;\n }\n\n // entry.type === 'storage_buffer'\n const storageBinding = nextBinding;\n nextBinding += 1;\n bglEntries.push({\n binding: storageBinding,\n visibility: FRAGMENT,\n buffer: { type: 'read-only-storage' },\n });\n resourceBindings.push({\n name: entry.name,\n parameter: entry.name,\n kind: 'storage-buffer',\n binding: storageBinding,\n });\n }\n\n const totalBytes = uboCursor === 0 ? 0 : alignUp(uboCursor, 16);\n const layoutIdentity = sha256LayoutIdentity({\n numericMembers,\n coordinateRecords,\n resourceBindings,\n totalBytes,\n });\n\n return {\n bglEntries,\n uboLayout: { entries: uboFields, totalBytes },\n numericMembers,\n coordinateRecords,\n resourceBindings,\n totalBytes,\n layoutIdentity,\n textureFieldNames,\n samplerForTexture,\n userRegionBindingEnd: nextBinding,\n };\n}\n\nfunction freezeParamSchema(schema: readonly ParamSchemaEntry[]): readonly ParamSchemaEntry[] {\n return Object.freeze(\n schema.map((entry) => {\n const defaultValue = cloneAndFreezeSchemaValue(entry.default);\n return Object.freeze({\n ...entry,\n ...(entry.default === undefined ? {} : { default: defaultValue }),\n }) as ParamSchemaEntry;\n }),\n );\n}\n\nfunction cloneAndFreezeSchemaValue(value: unknown): unknown {\n if (Array.isArray(value)) {\n return Object.freeze(value.map((item) => cloneAndFreezeSchemaValue(item)));\n }\n if (typeof value === 'object' && value !== null) {\n return Object.freeze(\n Object.fromEntries(\n Object.entries(value).map(([key, item]) => [key, cloneAndFreezeSchemaValue(item)]),\n ),\n );\n }\n return value;\n}\n\nfunction freezeDerivedMaterialInterface(\n derived: DerivedMaterialInterface,\n): DerivedMaterialInterface {\n deepFreeze(derived.bglEntries);\n deepFreeze(derived.uboLayout);\n deepFreeze(derived.numericMembers);\n deepFreeze(derived.coordinateRecords);\n deepFreeze(derived.resourceBindings);\n return Object.freeze({\n ...derived,\n textureFieldNames: new ImmutableSetView(derived.textureFieldNames),\n samplerForTexture: new ImmutableMapView(derived.samplerForTexture),\n });\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (typeof value !== 'object' || value === null || Object.isFrozen(value)) return value;\n for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child);\n return Object.freeze(value);\n}\n\nclass ImmutableSetView<T> implements ReadonlySet<T> {\n readonly #values: Set<T>;\n\n constructor(values: Iterable<T>) {\n this.#values = new Set(values);\n Object.freeze(this);\n }\n\n get size(): number {\n return this.#values.size;\n }\n\n has(value: T): boolean {\n return this.#values.has(value);\n }\n\n entries(): SetIterator<[T, T]> {\n return this.#values.entries();\n }\n\n keys(): SetIterator<T> {\n return this.#values.keys();\n }\n\n values(): SetIterator<T> {\n return this.#values.values();\n }\n\n forEach(callbackfn: (value: T, value2: T, set: ReadonlySet<T>) => void, thisArg?: unknown): void {\n for (const value of this.#values) callbackfn.call(thisArg, value, value, this);\n }\n\n [Symbol.iterator](): SetIterator<T> {\n return this.#values[Symbol.iterator]();\n }\n}\n\nclass ImmutableMapView<K, V> implements ReadonlyMap<K, V> {\n readonly #values: Map<K, V>;\n\n constructor(values: Iterable<readonly [K, V]>) {\n this.#values = new Map(values);\n Object.freeze(this);\n }\n\n get size(): number {\n return this.#values.size;\n }\n\n get(key: K): V | undefined {\n return this.#values.get(key);\n }\n\n has(key: K): boolean {\n return this.#values.has(key);\n }\n\n entries(): MapIterator<[K, V]> {\n return this.#values.entries();\n }\n\n keys(): MapIterator<K> {\n return this.#values.keys();\n }\n\n values(): MapIterator<V> {\n return this.#values.values();\n }\n\n forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: unknown): void {\n for (const [key, value] of this.#values) callbackfn.call(thisArg, value, key, this);\n }\n\n [Symbol.iterator](): MapIterator<[K, V]> {\n return this.#values[Symbol.iterator]();\n }\n}\n\nexport function inferMaterialParameterKind(\n entry: ParamSchemaEntry,\n): MaterialParameterProjection['kind'] {\n if (NUMERIC_TYPES.has(entry.type)) return 'numeric';\n if (TEXTURE_VIEW_TYPES.has(entry.type)) return 'texture';\n if (entry.type === 'storage_buffer') return 'storage-buffer';\n return 'sampler';\n}\n\nfunction sha256LayoutIdentity(value: {\n readonly numericMembers: readonly DerivedNumericMember[];\n readonly coordinateRecords: readonly MaterialCoordinateRecordLayout[];\n readonly resourceBindings: readonly MaterialResourceBindingLayout[];\n readonly totalBytes: number;\n}): string {\n const canonical = JSON.stringify({\n version: 1,\n numericMembers: value.numericMembers,\n coordinateRecords: value.coordinateRecords,\n resourceBindings: value.resourceBindings,\n totalBytes: value.totalBytes,\n });\n return `sha256-${sha256(canonical)}`;\n}\n\nconst SHA256_K = [\n 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,\n] as const;\n\nfunction sha256(input: string): string {\n const bytes = new TextEncoder().encode(input);\n const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;\n const padded = new Uint8Array(paddedLength);\n padded.set(bytes);\n padded[bytes.length] = 0x80;\n const view = new DataView(padded.buffer);\n view.setUint32(paddedLength - 4, bytes.length * 8, false);\n let hash = new Uint32Array([\n 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\n ]);\n for (let block = 0; block < padded.length; block += 64) {\n const words = new Uint32Array(64);\n for (let index = 0; index < 16; index += 1)\n words[index] = view.getUint32(block + index * 4, false);\n for (let index = 16; index < 64; index += 1) {\n const a = words[index - 15] ?? 0;\n const b = words[index - 2] ?? 0;\n words[index] =\n (smallSigma1(b) + (words[index - 7] ?? 0) + smallSigma0(a) + (words[index - 16] ?? 0)) >>>\n 0;\n }\n let a = hash[0] ?? 0;\n let b = hash[1] ?? 0;\n let c = hash[2] ?? 0;\n let d = hash[3] ?? 0;\n let e = hash[4] ?? 0;\n let f = hash[5] ?? 0;\n let g = hash[6] ?? 0;\n let h = hash[7] ?? 0;\n for (let index = 0; index < 64; index += 1) {\n const choose = (e & f) ^ (~e & g);\n const majority = (a & b) ^ (a & c) ^ (b & c);\n const t1 = (h + bigSigma1(e) + choose + (SHA256_K[index] ?? 0) + (words[index] ?? 0)) >>> 0;\n const t2 = (bigSigma0(a) + majority) >>> 0;\n [h, g, f, e, d, c, b, a] = [g, f, e, (d + t1) >>> 0, c, b, a, (t1 + t2) >>> 0];\n }\n const state = [a, b, c, d, e, f, g, h];\n hash = new Uint32Array(hash.map((value, index) => ((value + (state[index] ?? 0)) >>> 0) >>> 0));\n }\n return Array.from(hash, (word) => word.toString(16).padStart(8, '0')).join('');\n}\n\nfunction rotateRight(value: number, shift: number): number {\n return (value >>> shift) | (value << (32 - shift));\n}\n\nfunction bigSigma0(value: number): number {\n return rotateRight(value, 2) ^ rotateRight(value, 13) ^ rotateRight(value, 22);\n}\n\nfunction bigSigma1(value: number): number {\n return rotateRight(value, 6) ^ rotateRight(value, 11) ^ rotateRight(value, 25);\n}\n\nfunction smallSigma0(value: number): number {\n return rotateRight(value, 7) ^ rotateRight(value, 18) ^ (value >>> 3);\n}\n\nfunction smallSigma1(value: number): number {\n return rotateRight(value, 17) ^ rotateRight(value, 19) ^ (value >>> 10);\n}\n\n/**\n * The three user-region material texture fields whose handles flow through the\n * paramSchema-driven extract filter (`validateTextureHandle`). They map to the\n * fixed `@group(1) @binding(2/4/6)` slots in the standard material BGL.\n *\n * `emissiveTexture` / `occlusionTexture` are deliberately EXCLUDED: they live\n * in the engine-managed lightmap injection region (`appendInjection`,\n * bindings 14..17), are sampled by `default-standard-pbr` without a schema\n * entry, and are never filtered by `validateTextureHandle`. Including them\n * would false-positive the engine's own PBR shader.\n */\nconst USER_REGION_TEXTURE_FIELDS: readonly string[] = [\n 'baseColorTexture',\n 'metallicRoughnessTexture',\n 'normalTexture',\n];\n\n/** Strip `//` line comments and block comments before scanning WGSL source. */\nfunction stripWgslComments(source: string): string {\n return source.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/[^\\n]*/g, '');\n}\n\n/**\n * Detect user-region material textures a shader actually `textureSample`s but\n * its paramSchema fails to declare as a texture entry.\n *\n * This is the runtime (register-time) counterpart of the build-time superset\n * gate (`compareParamSchemaSuperset`): user shaders registered directly via\n * `ShaderRegistry.installMaterialArtifact` bypass the vite-plugin-shader\n * reflection path, so an under-declared schema would otherwise let the extract\n * stage's `validateTextureHandle` silently drop the sampled texture's handle\n * and fall back to the default white texture (charter P3 violation — the bug\n * that turned the LearnOpenGL 4.3 blending demo's grass + windows opaque white;\n * see docs/handover/2026-06-19-blending-transparency-regression-bisect.md).\n *\n * Returns the field names that are sampled-but-undeclared (empty = consistent).\n * Scan is name-based on the WGSL var passed as the first `textureSample*`\n * argument; comments are stripped first so a commented-out sample never trips\n * the check. Only the three `USER_REGION_TEXTURE_FIELDS` participate, so a\n * shader that merely *declares* the standard binding layout without sampling it\n * (e.g. an outline / depth-viz shader reusing the PBR BGL) is not flagged.\n */\nexport function findUndeclaredSampledTextures(\n wgslSource: string,\n schema: readonly ParamSchemaEntry[],\n): readonly string[] {\n const declared = derive(schema).textureFieldNames;\n const clean = stripWgslComments(wgslSource);\n const sampleRe = /textureSample[A-Za-z]*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)/g;\n const sampled = new Set<string>();\n for (let m = sampleRe.exec(clean); m !== null; m = sampleRe.exec(clean)) {\n const name = m[1];\n if (name !== undefined) sampled.add(name);\n }\n return USER_REGION_TEXTURE_FIELDS.filter((f) => sampled.has(f) && !declared.has(f));\n}\n","// @forgeax/engine-types - producer-owned asset catalog contract.\n//\n// This is the shared POD boundary between an engine asset producer and any\n// consumer. Consumers must use these facts instead of deriving origin from a\n// DDC URL, filename suffix, or catalog position.\n\nexport type AssetSubjectType = 'asset' | 'package' | 'resource';\n\n/** The producer-owned subject behind a catalog row. */\nexport type CatalogSubject = 'internal-asset' | 'imported-output';\n\n/** Whether the runtime projection is validated directly or cooked. */\nexport type CookExecution = 'direct' | 'cooked';\n\n/** Derived lifecycle states exposed by the catalog. */\nexport type CatalogLifecycle = 'missing' | 'cooking' | 'current' | 'stale' | 'failed';\n\n/** Usage derived from producer refs and build-time content reads. */\nexport type AssetPublicationEvidenceUsage = 'reference' | 'content' | 'both';\n\n/** One external dependency observed while publishing a source package. */\nexport interface AssetPublicationExternalEvidence {\n readonly guid: string;\n readonly usage: AssetPublicationEvidenceUsage;\n readonly generation?: number;\n readonly digest?: string;\n}\n\n/** One ordinary asset output in an atomic source-package publication. */\nexport interface AssetPublicationOutput {\n readonly guid: string;\n readonly sourceKey: string;\n readonly kind: string;\n readonly digest: string;\n /** GUIDs emitted by the owning ordinary producer, in stable order. */\n readonly refs: readonly string[];\n}\n\n/** Receipt facts that prove the complete output set and dependency closure. */\nexport interface AssetPublicationReceipt {\n readonly schemaVersion: 'asset-publication-receipt/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly inputFingerprint: string;\n readonly outputDigest: string;\n readonly outputSetDigest: string;\n readonly externalEvidence: readonly AssetPublicationExternalEvidence[];\n}\n\n/** Stable locator for current or last-known-good publication recovery. */\nexport interface AssetPublicationLocator {\n readonly generation: number;\n readonly digest: string;\n readonly outputSetDigest: string;\n readonly packageUrl: string;\n readonly receiptKey: string;\n}\n\nexport type AssetPublicationFailureStage =\n | 'source'\n | 'output'\n | 'receipt'\n | 'route'\n | 'catalog'\n | 'cancelled';\n\n/** Machine-readable failure and recovery facts; messages are not a protocol. */\nexport interface AssetPublicationFailure {\n readonly code: string;\n readonly stage: AssetPublicationFailureStage;\n readonly sourcePath: string;\n readonly sourceRevision?: string;\n readonly generation?: number;\n readonly outputGuid?: string;\n readonly reason: string;\n}\n\n/** Actions a consumer can execute after a candidate publication is rejected. */\nexport interface AssetPublicationRecovery {\n readonly retryable: boolean;\n readonly preserveCurrent: boolean;\n readonly useLastKnownGood: boolean;\n readonly actions: readonly string[];\n}\n\n/**\n * Engine-owned publication SSOT shared by pack, import, Catalog, and consumers.\n * A publication is valid only when receipt and every output belong to one\n * source revision, generation, digest, and output-set digest tuple.\n */\nexport interface AssetPublicationEnvelope {\n readonly schemaVersion: 'asset-publication/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly generation: number;\n readonly digest: string;\n readonly outputSetDigest: string;\n readonly outputs: readonly AssetPublicationOutput[];\n readonly receipt: AssetPublicationReceipt;\n readonly externalEvidence: readonly AssetPublicationExternalEvidence[];\n readonly failureStage?: AssetPublicationFailureStage;\n readonly failure?: AssetPublicationFailure;\n readonly current?: AssetPublicationLocator;\n readonly lastKnownGood?: AssetPublicationLocator;\n readonly recovery?: AssetPublicationRecovery;\n}\n\n/**\n * Immutable source-level identity carried by an authored generated Scene mount.\n * A mount is consumable only when every member resolves from this complete\n * publication tuple; no single output GUID or generation number is sufficient.\n */\nexport interface ScenePublicationFence {\n readonly schemaVersion: 'scene-publication-fence/1';\n readonly sourcePath: string;\n readonly sourceRevision: string;\n readonly publicationGeneration: number;\n readonly outputDigest: string;\n readonly outputSetDigest: string;\n readonly receiptIdentity: string;\n}\n\nexport type CatalogOperationName =\n | 'preview'\n | 'save'\n | 'rebuild'\n | 'sourceOverride'\n | 'instanceOverride'\n | 'promote';\n\nexport interface CatalogOperationDescriptor {\n readonly operation: CatalogOperationName;\n readonly enabled: boolean;\n readonly reason?: string;\n}\n\nexport type CatalogOperations = Readonly<Record<CatalogOperationName, CatalogOperationDescriptor>>;\n\nexport interface CatalogProjectionInput {\n readonly subject: CatalogSubject;\n readonly execution: CookExecution;\n readonly lifecycle: CatalogLifecycle;\n}\n\n/** The explicit three-axis projection consumed by AI-facing catalog clients. */\nexport interface CatalogProjection extends CatalogProjectionInput {\n readonly operations: CatalogOperations;\n readonly lastKnownGood?: {\n readonly packageUrl: string;\n readonly receiptUrl?: string;\n };\n}\n\n/**\n * Derive operation descriptors from catalog facts only.\n *\n * `kind`, paths, and diagnostic messages are intentionally absent from this\n * function: a consumer receives a complete operation matrix and can branch\n * on `enabled` without reimplementing producer policy.\n */\nexport function catalogOperationsFor(input: CatalogProjectionInput): CatalogOperations {\n const imported = input.subject === 'imported-output';\n const current = input.lifecycle === 'current';\n const ready = current && (input.execution === 'direct' || input.execution === 'cooked');\n const canRebuild = input.execution === 'cooked';\n const canPreview = input.execution === 'cooked' && input.lifecycle !== 'missing';\n const operation = (name: CatalogOperationName, enabled: boolean, reason?: string) => ({\n operation: name,\n enabled,\n ...(reason === undefined ? {} : { reason }),\n });\n\n return {\n preview: operation('preview', canPreview, canPreview ? undefined : 'no projection to preview'),\n save: operation(\n 'save',\n !imported && input.execution === 'direct' && ready,\n imported ? 'imported output is read-only' : 'direct projection is not current',\n ),\n rebuild: operation(\n 'rebuild',\n canRebuild,\n canRebuild ? undefined : 'direct assets do not require a cook',\n ),\n sourceOverride: operation(\n 'sourceOverride',\n imported && canRebuild,\n imported\n ? canRebuild\n ? undefined\n : 'cooked projection is not available'\n : 'only imported output has a source override',\n ),\n instanceOverride: operation(\n 'instanceOverride',\n imported && current,\n imported\n ? current\n ? undefined\n : 'projection is not current'\n : 'only imported output has an instance override',\n ),\n promote: operation(\n 'promote',\n imported && current,\n imported\n ? current\n ? undefined\n : 'projection is not current'\n : 'internal assets are already authored',\n ),\n };\n}\n\n/** Reject impossible axis combinations before a catalog row is published. */\nexport function isCatalogProjectionValid(input: CatalogProjection): boolean {\n if (input.execution === 'direct' && input.lifecycle !== 'current') return false;\n if (input.subject === 'imported-output' && input.execution !== 'cooked') return false;\n return Object.entries(input.operations).every(\n ([name, descriptor]) => name === descriptor.operation,\n );\n}\n\nexport interface AssetAuthoringUnavailableReason {\n readonly code: 'unsupported-asset-kind' | 'missing-producer-capability';\n readonly hint: string;\n}\n\n/** Engine-facing operation shape exposed by a producer-owned catalog row. */\nexport type AssetPlacementCapability =\n | { readonly operation: 'spawnEntity' }\n | { readonly operation: 'addSceneAssetToScene' }\n | { readonly operation: 'unavailable'; readonly reason: AssetAuthoringUnavailableReason };\n\nexport interface AssetBindingTarget {\n readonly component: string;\n readonly field: string;\n readonly assetType: string;\n readonly cardinality: 'single' | 'array';\n}\n\n/**\n * One projection exposed by the producer-owned UI authoring contract.\n *\n * `supported` describes a real engine seam; `unavailable` is deliberately\n * structured so an editor or AI client cannot silently invent a consumer-side\n * implementation for a missing producer capability.\n */\nexport type UiAuthoringProjection =\n | {\n readonly status: 'supported';\n readonly operation:\n | 'createUiPreviewSession'\n | 'mountUi'\n | 'gameProjection'\n | 'dom-native'\n | 'ui-artifact-companion';\n readonly contractVersion: '1';\n }\n | {\n readonly status: 'unavailable';\n readonly reason: AssetAuthoringUnavailableReason;\n };\n\n/**\n * Versioned UI authoring facts published beside every `kind: 'ui'` catalog\n * row. This is a protocol descriptor, not a promise that the editor may reach\n * into a game world: runtime state/action/read semantics remain owned by the\n * game projection registrar and the UI mount/preview seams remain owned by the\n * engine UI package.\n */\nexport interface UiAuthoringCapability {\n readonly contractVersion: '1';\n readonly profileVersion: '1';\n readonly preview: {\n readonly operation: 'createUiPreviewSession';\n readonly lifecycle: 'open-rebuild-retry-dispose';\n };\n readonly mount: {\n readonly operation: 'mountUi';\n readonly lifecycle: 'mount-dispose';\n readonly actionPort: 'onAction';\n };\n readonly state: UiAuthoringProjection;\n readonly actions: UiAuthoringProjection;\n readonly reads: UiAuthoringProjection;\n readonly input: UiAuthoringProjection;\n readonly navigation: UiAuthoringProjection;\n readonly font: UiAuthoringProjection;\n readonly localization: UiAuthoringProjection;\n}\n\nexport type AssetBindingCapability =\n | {\n readonly operation: 'bindAssetRef' | 'createMaterialThenBindAssetRef';\n readonly target: AssetBindingTarget;\n readonly requiredSlots: 1;\n }\n | { readonly operation: 'unavailable'; readonly reason: AssetAuthoringUnavailableReason };\n\n/** Producer-owned placement and binding facts for one catalog asset. */\nexport interface AssetAuthoringCapability {\n readonly placement: AssetPlacementCapability;\n readonly binding: AssetBindingCapability;\n /** Present for producer-owned UI assets; absent for unrelated kinds. */\n readonly ui?: UiAuthoringCapability;\n readonly sourceOverrides?: readonly SourceOverrideDescriptor[];\n}\n\nconst UI_AUTHORING_CAPABILITY: UiAuthoringCapability = {\n contractVersion: '1',\n profileVersion: '1',\n preview: {\n operation: 'createUiPreviewSession',\n lifecycle: 'open-rebuild-retry-dispose',\n },\n mount: {\n operation: 'mountUi',\n lifecycle: 'mount-dispose',\n actionPort: 'onAction',\n },\n state: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n actions: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n reads: { status: 'supported', operation: 'gameProjection', contractVersion: '1' },\n input: { status: 'supported', operation: 'dom-native', contractVersion: '1' },\n navigation: { status: 'supported', operation: 'dom-native', contractVersion: '1' },\n font: { status: 'supported', operation: 'ui-artifact-companion', contractVersion: '1' },\n localization: {\n status: 'unavailable',\n reason: {\n code: 'missing-producer-capability',\n hint: 'UI localization resources are not yet published through the UI authoring contract.',\n },\n },\n};\n\n/** Built-in defaults for legacy rows that do not carry an explicit override. */\nexport function authoringCapabilityForAssetKind(kind: string): AssetAuthoringCapability {\n switch (kind) {\n case 'ui':\n return {\n placement: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'UI assets mount through the UI runtime and are not ECS scene placements.',\n },\n },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'UI assets bind through their producer-owned UI runtime contract.',\n },\n },\n ui: UI_AUTHORING_CAPABILITY,\n };\n case 'scene':\n return {\n placement: { operation: 'addSceneAssetToScene' },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: 'Scene assets are placed as a scene mount.',\n },\n },\n };\n case 'mesh':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'MeshFilter',\n field: 'assetHandle',\n assetType: 'MeshAsset',\n cardinality: 'single',\n },\n requiredSlots: 1,\n },\n };\n case 'material':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'MeshRenderer',\n field: 'materials',\n assetType: 'MaterialAsset',\n cardinality: 'array',\n },\n requiredSlots: 1,\n },\n };\n case 'texture':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'createMaterialThenBindAssetRef',\n target: {\n component: 'MeshRenderer',\n field: 'materials',\n assetType: 'MaterialAsset',\n cardinality: 'array',\n },\n requiredSlots: 1,\n },\n };\n case 'particle-effect':\n return {\n placement: { operation: 'spawnEntity' },\n binding: {\n operation: 'bindAssetRef',\n target: {\n component: 'ParticleEffectPlayer',\n field: 'effect',\n assetType: 'ParticleEffectAsset',\n cardinality: 'single',\n },\n requiredSlots: 1,\n },\n };\n default:\n return {\n placement: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: `No placement capability is published for asset kind '${kind}'.`,\n },\n },\n binding: {\n operation: 'unavailable',\n reason: {\n code: 'unsupported-asset-kind',\n hint: `No binding capability is published for asset kind '${kind}'.`,\n },\n },\n };\n }\n}\n\n/** Stable producer subject identity used by relations and diagnostics. */\nexport interface AssetSubjectRef {\n readonly type: AssetSubjectType;\n readonly id: string;\n}\n\n/** Provider identity carried with producer-owned facts; not a catalog locator. */\nexport interface ProviderProvenance {\n readonly provider: string;\n readonly version: string;\n readonly source?: string;\n}\n\n/** Producer-owned JSON payload for one stable imported-output source key. */\nexport type SourceOverridePayload = Readonly<Record<string, unknown>>;\n\n/** Optional Meta author facts keyed by the producer's stable sourceKey. */\nexport type SourceOverrideMap = Readonly<Record<string, SourceOverridePayload>>;\n\nexport interface SourceOverrideDescriptor {\n readonly sourceKey: string;\n /** Producer-owned semantic used by authoring hosts for catalog-aware validation. */\n readonly semantic?: 'mesh-material-slot-defaults';\n readonly payloadSchema?: unknown;\n}\n\n/** Shared payload contract explicitly published by Mesh-producing importers. */\nexport const MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA = {\n type: 'object',\n properties: {\n materialSlots: {\n type: 'array',\n items: {\n type: 'object',\n properties: {\n slotName: { type: 'string', minLength: 1 },\n sourceKey: { type: 'string', minLength: 1 },\n defaultMaterialGuid: { type: 'string' },\n },\n required: ['slotName'],\n additionalProperties: true,\n },\n },\n materialSlotDefaultOverrides: {\n type: 'object',\n additionalProperties: { type: 'string', nullable: true },\n },\n },\n additionalProperties: true,\n} as const;\n\nexport type SourceOverrideErrorCode =\n | 'unknown-source-key'\n | 'duplicate-source-key'\n | 'invalid-source-overrides'\n | 'invalid-source-override-payload';\n\nexport interface SourceOverrideDiagnostic {\n readonly code: SourceOverrideErrorCode;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n}\n\nexport type SourceOverrideValidationResult =\n | { readonly ok: true; readonly value: SourceOverrideMap | undefined }\n | { readonly ok: false; readonly error: SourceOverrideDiagnostic };\n\nfunction sourceOverrideError(\n code: SourceOverrideErrorCode,\n expected: string,\n hint: string,\n actual?: string,\n): SourceOverrideValidationResult {\n return {\n ok: false,\n error: { code, expected, hint, ...(actual === undefined ? {} : { actual }) },\n };\n}\n\nfunction isSourceOverridePayload(value: unknown): value is SourceOverridePayload {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** Canonicalize legacy/empty override maps without changing producer payloads. */\nexport function canonicalizeSourceOverrides(value: unknown): SourceOverrideMap | undefined {\n if (value === undefined) return undefined;\n if (!isSourceOverridePayload(value)) return undefined;\n const keys = Object.keys(value);\n if (keys.length === 0) return undefined;\n return Object.fromEntries(keys.sort().map((key) => [key, value[key]])) as SourceOverrideMap;\n}\n\nfunction validateSourceOverrideEntry(\n sourceKey: string,\n payload: unknown,\n declared: ReadonlySet<string>,\n seen: Set<string>,\n): SourceOverrideDiagnostic | undefined {\n if (seen.has(sourceKey)) {\n return {\n code: 'duplicate-source-key',\n expected: 'sourceKey values to be unique within sourceOverrides',\n hint: 'remove the duplicate source override',\n actual: sourceKey,\n };\n }\n seen.add(sourceKey);\n if (!declared.has(sourceKey)) {\n return {\n code: 'unknown-source-key',\n expected: 'sourceKey to be declared by the producer topology',\n hint: 'request a fresh Catalog topology before writing Meta',\n actual: sourceKey,\n };\n }\n if (!isSourceOverridePayload(payload)) {\n return {\n code: 'invalid-source-override-payload',\n expected: 'each source override payload to be a producer-owned object',\n hint: 'validate the payload with the producer schema',\n actual: sourceKey,\n };\n }\n return undefined;\n}\n\nfunction validateSourceOverrideEntries(\n entries: readonly (readonly [string, unknown])[],\n declared: ReadonlySet<string>,\n): SourceOverrideValidationResult {\n const seen = new Set<string>();\n for (const [sourceKey, payload] of entries) {\n const error = validateSourceOverrideEntry(sourceKey, payload, declared, seen);\n if (error !== undefined) return { ok: false, error };\n }\n return { ok: true, value: canonicalizeSourceOverrides(Object.fromEntries(entries)) };\n}\n\n/** Validate source override identity while leaving payload interpretation to the producer. */\nexport function validateSourceOverrideMap(\n value: unknown,\n declaredSourceKeys: readonly string[],\n): SourceOverrideValidationResult {\n const declared = new Set<string>();\n for (const sourceKey of declaredSourceKeys) {\n if (declared.has(sourceKey)) {\n return sourceOverrideError(\n 'duplicate-source-key',\n 'sourceKey values declared by a producer to be unique',\n 'repair the producer topology before publishing Meta',\n sourceKey,\n );\n }\n declared.add(sourceKey);\n }\n if (value === undefined) return { ok: true, value: undefined };\n if (Array.isArray(value)) {\n const entries: (readonly [string, unknown])[] = [];\n for (const item of value) {\n if (!Array.isArray(item) || item.length !== 2 || typeof item[0] !== 'string') {\n return sourceOverrideError(\n 'invalid-source-overrides',\n 'sourceOverrides to be an object keyed by sourceKey',\n 'pass a producer-owned source override map',\n );\n }\n entries.push([item[0], item[1]]);\n }\n return validateSourceOverrideEntries(entries, declared);\n }\n if (!isSourceOverridePayload(value)) {\n return sourceOverrideError(\n 'invalid-source-overrides',\n 'sourceOverrides to be an object keyed by sourceKey',\n 'pass a producer-owned source override map',\n );\n }\n return validateSourceOverrideEntries(Object.entries(value), declared);\n}\n\n/** Monotonic producer observation used to validate catalog continuity. */\nexport interface ResourceRevision {\n readonly digest: string;\n readonly observedAt: number;\n readonly rootId: string;\n}\n\nexport type AssetRelationType =\n | 'references'\n | 'reads'\n | 'depends-on'\n | 'owns'\n | 'contains'\n | 'produces'\n | 'materialized-as'\n | (string & {});\n\nexport interface AssetRelationPolicy {\n readonly ownership?: 'owned' | 'shared';\n readonly lifecycle?: 'authored' | 'derived';\n readonly strength?: 'required' | 'optional';\n}\n\n/** Structured graph edge emitted by a producer; consumers must preserve its fields. */\nexport interface AssetRelation {\n readonly from: AssetSubjectRef;\n readonly to: AssetSubjectRef;\n readonly type: AssetRelationType;\n readonly policy?: AssetRelationPolicy;\n readonly provenance: ProviderProvenance;\n}\n\nexport type CatalogDiagnosticSeverity = 'info' | 'warning' | 'blocking';\n\n/** Machine-readable catalog problem; consumers branch on fields, never message text. */\nexport interface CatalogDiagnostic {\n readonly code: string;\n readonly severity: CatalogDiagnosticSeverity;\n readonly message?: string;\n readonly subject?: AssetSubjectRef;\n readonly expected?: string;\n readonly actual?: string;\n readonly hint?: string;\n readonly authority?: 'producer' | 'pack' | 'catalog';\n readonly evidence?: readonly AssetSubjectRef[];\n readonly recoveryIntents?: readonly string[];\n}\n\n/** Closed set of contract failures returned by producer validation. */\nexport type ProducerContractErrorCode =\n | SourceOverrideErrorCode\n | TopologyConflictReason\n | 'invalid-source-key'\n | 'invalid-source-index'\n | 'invalid-producer-fact';\n\n/** Structured producer validation failure with its owning authority. */\nexport interface ProducerContractDiagnostic {\n readonly code: ProducerContractErrorCode;\n readonly subject: AssetSubjectRef;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n readonly authority: 'producer' | 'pack';\n}\n\n/** Result boundary for producer validation; success and failure are discriminated by `ok`. */\nexport type ProducerContractResult<T> =\n | { readonly ok: true; readonly value: T }\n | { readonly ok: false; readonly error: ProducerContractDiagnostic };\n\n/** Canonical producer output declaration used for topology matching and recovery. */\nexport interface ImportedOutputDeclaration {\n readonly guid: string;\n readonly sourceKey?: string;\n readonly sourceIndex: number;\n readonly kind: string;\n readonly name?: string;\n /** New kinds that may reuse this output's prior GUID. */\n readonly compatiblePreviousKinds?: readonly string[];\n}\n\nexport type ProposedOutput = ImportedOutputDeclaration;\n\nexport type ExistingOutput = ImportedOutputDeclaration;\n\nexport interface KindChange {\n readonly guid: string;\n readonly oldKind: string;\n readonly newKind: string;\n readonly sourceKey?: string;\n readonly action: 'remove-add' | 'preserve-guid';\n}\n\nexport type TopologyConflictReason =\n | 'duplicate-source-key'\n | 'missing-source-key'\n | 'source-index-ambiguous';\n\nexport interface MatchConflict {\n readonly reason: TopologyConflictReason;\n readonly sourceKey?: string;\n readonly previous: readonly ExistingOutput[];\n readonly next: readonly ProposedOutput[];\n}\n\nexport interface TopologyPreserved {\n readonly guid: string;\n readonly oldKey: string;\n readonly newKey: string;\n}\n\nexport interface TopologyDiff {\n readonly preserved: readonly TopologyPreserved[];\n readonly added: readonly ProposedOutput[];\n readonly removed: readonly ExistingOutput[];\n readonly changedKind: readonly KindChange[];\n readonly ambiguous: readonly MatchConflict[];\n}\n","import type { AssetPublicationTuple } from './asset.js';\nimport type {\n AssetAuthoringCapability,\n AssetPublicationEnvelope,\n AssetRelation,\n CatalogDiagnostic,\n CatalogLifecycle,\n CatalogProjection,\n CatalogSubject,\n CookExecution,\n ProviderProvenance,\n ResourceRevision,\n SourceOverrideDescriptor,\n SourceOverrideMap,\n TopologyDiff,\n} from './asset-producer';\nimport { err, ok, type Result } from './result';\n\nexport type {\n AssetPublicationEnvelope,\n AssetPublicationEvidenceUsage,\n AssetPublicationExternalEvidence,\n AssetPublicationFailure,\n AssetPublicationFailureStage,\n AssetPublicationLocator,\n AssetPublicationOutput,\n AssetPublicationReceipt,\n AssetPublicationRecovery,\n CatalogLifecycle,\n CatalogProjection,\n CatalogSubject,\n CookExecution,\n} from './asset-producer';\n\n/**\n * Strict contract row for the Catalog projection.\n *\n * This is evidence for an AI consumer, not authoring authority: a Catalog row\n * projects producer facts and runtime navigation while preserving lifecycle,\n * execution, and sourceKey distinctions.\n */\nexport interface CatalogEntryV2 extends AssetPublicationTuple {\n readonly guid: string;\n readonly packageUrl: string;\n readonly kind: string;\n readonly sourcePath: string;\n readonly subject: CatalogSubject;\n readonly execution: CookExecution;\n readonly lifecycle: CatalogLifecycle;\n readonly projection: CatalogProjection;\n}\n/** One producer revision point in a catalog continuity window. */\nexport interface CatalogRevisionPoint {\n readonly rootId: string;\n readonly revision: number;\n}\n\n/** Baseline/current revision sets used to reject stale or partial updates. */\nexport interface CatalogRevisionWindow {\n readonly baseline: readonly CatalogRevisionPoint[];\n readonly current: readonly CatalogRevisionPoint[];\n}\n\n/** One stable row from a development or build catalog snapshot. */\nexport interface CatalogEntry {\n readonly guid: string;\n /** GUID-to-pack navigation only; artifact paths live inside Pack v2. */\n readonly packageUrl: string;\n readonly kind: string;\n /** Producer-owned placement/binding facts; absent only on legacy rows. */\n readonly authoring?: AssetAuthoringCapability;\n /** Source declaration navigation for diagnostics, not runtime content. */\n readonly sourcePath: string;\n /** Stable package identity; path is a locator, never the package identity. */\n readonly packageId?: string;\n /** Producer-owned importer/provider identity and version. */\n readonly provenance?: ProviderProvenance;\n /** Producer-owned resource/package revision used for conflict checks. */\n readonly revision?: ResourceRevision;\n /** Stable producer key for imported-output topology matching; never infer it from sourceIndex. */\n readonly sourceKey?: string;\n /** Producer-declared output position; never used as identity when sourceKey exists. */\n readonly sourceIndex?: number;\n /** Producer-owned author facts carried through the catalog without interpretation. */\n readonly sourceOverrides?: SourceOverrideMap;\n readonly sourceOverrideDescriptors?: readonly SourceOverrideDescriptor[];\n /** Typed graph edges emitted by the producer. */\n readonly relations?: readonly AssetRelation[];\n /** Structured producer diagnostics; consumers must not parse messages. */\n readonly diagnostics?: readonly CatalogDiagnostic[];\n readonly name?: string;\n /** Optional navigation to the producer-owned cook receipt. */\n readonly cookReceiptUrl?: string;\n readonly refs?: readonly string[];\n /** Explicit producer-owned runtime projection axes. */\n readonly subject?: CatalogSubject;\n readonly execution?: CookExecution;\n readonly lifecycle?: CatalogLifecycle;\n readonly projection?: CatalogProjection;\n /** Complete Engine publication tuple, when this row came from a source package. */\n readonly publication?: AssetPublicationEnvelope;\n}\n\n/**\n * A folded, neutral set of catalog-row changes keyed by stable GUID.\n *\n * `authority` and `diagnostics` tell AI-readable consumers whether the delta\n * is safe to apply; a degraded delta carries no identity-bearing changes.\n */\nexport interface CatalogDelta {\n /** Runtime realm identity for dev publications; absent for immutable legacy builds. */\n readonly scopeId?: string;\n readonly generation?: number;\n readonly added: readonly CatalogEntry[];\n readonly changed: readonly CatalogEntry[];\n readonly removed: readonly CatalogEntry['guid'][];\n /** Optional topology evidence for imported-output changes in this delta. */\n readonly topology?: readonly TopologyDiff[];\n /** Present when a watch revision was supplied for continuity validation. */\n readonly authority?: 'authoritative' | 'degraded';\n /** Machine-readable continuity or topology diagnostics. */\n readonly diagnostics?: readonly CatalogDiagnostic[];\n readonly revisions?: CatalogRevisionWindow;\n}\n\nexport interface CatalogDeltaValidationError {\n readonly code: 'catalog-delta-invalid';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly field: string };\n}\n\nfunction catalogInvalid(field: string): Result<never, CatalogDeltaValidationError> {\n return err({\n code: 'catalog-delta-invalid',\n expected: 'a CatalogDelta with complete row identity and string removals',\n hint: 'discard the delta and enumerate a verified catalog snapshot',\n detail: { field },\n });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\nfunction isStringArray(value: unknown): value is readonly string[] {\n return Array.isArray(value) && value.every(isNonEmptyString);\n}\n\nfunction isSubjectRef(value: unknown): boolean {\n return (\n isRecord(value) &&\n (value.type === 'asset' || value.type === 'package' || value.type === 'resource') &&\n isNonEmptyString(value.id)\n );\n}\n\nfunction isRevision(value: unknown): value is ResourceRevision {\n if (!isRecord(value)) return false;\n const observedAt = value.observedAt;\n return (\n isNonEmptyString(value.digest) &&\n typeof observedAt === 'number' &&\n Number.isSafeInteger(observedAt) &&\n observedAt >= 0 &&\n isNonEmptyString(value.rootId)\n );\n}\n\nfunction isDiagnostic(value: unknown): value is CatalogDiagnostic {\n return (\n isRecord(value) &&\n isNonEmptyString(value.code) &&\n (value.severity === 'info' || value.severity === 'warning' || value.severity === 'blocking') &&\n (value.message === undefined || typeof value.message === 'string') &&\n (value.subject === undefined || isSubjectRef(value.subject)) &&\n (value.expected === undefined || typeof value.expected === 'string') &&\n (value.actual === undefined || typeof value.actual === 'string') &&\n (value.hint === undefined || typeof value.hint === 'string') &&\n (value.authority === undefined ||\n value.authority === 'producer' ||\n value.authority === 'pack' ||\n value.authority === 'catalog') &&\n (value.evidence === undefined ||\n (Array.isArray(value.evidence) && value.evidence.every(isSubjectRef))) &&\n (value.recoveryIntents === undefined || isStringArray(value.recoveryIntents))\n );\n}\n\nfunction isRevisionWindow(value: unknown): value is CatalogRevisionWindow {\n if (!isRecord(value) || !Array.isArray(value.baseline) || !Array.isArray(value.current)) {\n return false;\n }\n const isPoint = (point: unknown): boolean =>\n isRecord(point) &&\n isNonEmptyString(point.rootId) &&\n typeof point.revision === 'number' &&\n Number.isSafeInteger(point.revision) &&\n point.revision >= 0;\n return value.baseline.every(isPoint) && value.current.every(isPoint);\n}\n\nfunction isTopologyDiff(value: unknown): boolean {\n if (!isRecord(value)) return false;\n return ['preserved', 'added', 'removed', 'changedKind', 'ambiguous'].every((field) =>\n Array.isArray(value[field]),\n );\n}\n\nfunction isCatalogEntry(value: unknown): value is CatalogEntry {\n if (!isRecord(value)) return false;\n if (\n !isNonEmptyString(value.guid) ||\n !isNonEmptyString(value.packageUrl) ||\n !isNonEmptyString(value.kind) ||\n !isNonEmptyString(value.sourcePath)\n ) {\n return false;\n }\n if (value.authoring !== undefined && !isRecord(value.authoring)) return false;\n if (value.packageId !== undefined && !isNonEmptyString(value.packageId)) return false;\n if (value.provenance !== undefined) {\n if (!isRecord(value.provenance)) return false;\n if (\n !isNonEmptyString(value.provenance.provider) ||\n !isNonEmptyString(value.provenance.version)\n ) {\n return false;\n }\n if (value.provenance.source !== undefined && !isNonEmptyString(value.provenance.source)) {\n return false;\n }\n }\n if (value.revision !== undefined && !isRevision(value.revision)) return false;\n if (value.sourceKey !== undefined && !isNonEmptyString(value.sourceKey)) return false;\n const sourceIndex = value.sourceIndex;\n if (\n sourceIndex !== undefined &&\n (typeof sourceIndex !== 'number' || !Number.isSafeInteger(sourceIndex) || sourceIndex < 0)\n )\n return false;\n if (value.sourceOverrides !== undefined && !isRecord(value.sourceOverrides)) return false;\n if (\n value.sourceOverrideDescriptors !== undefined &&\n !Array.isArray(value.sourceOverrideDescriptors)\n ) {\n return false;\n }\n if (value.relations !== undefined && !Array.isArray(value.relations)) return false;\n if (\n value.diagnostics !== undefined &&\n (!Array.isArray(value.diagnostics) || !value.diagnostics.every(isDiagnostic))\n )\n return false;\n if (value.name !== undefined && !isNonEmptyString(value.name)) return false;\n if (value.cookReceiptUrl !== undefined && !isNonEmptyString(value.cookReceiptUrl)) return false;\n if (value.refs !== undefined && !isStringArray(value.refs)) return false;\n if (\n value.subject !== undefined &&\n value.subject !== 'internal-asset' &&\n value.subject !== 'imported-output'\n ) {\n return false;\n }\n if (\n value.execution !== undefined &&\n value.execution !== 'direct' &&\n value.execution !== 'cooked'\n ) {\n return false;\n }\n if (\n value.lifecycle !== undefined &&\n !['missing', 'cooking', 'current', 'stale', 'failed'].includes(value.lifecycle as string)\n ) {\n return false;\n }\n if (value.projection !== undefined && !isRecord(value.projection)) return false;\n return value.publication === undefined || isRecord(value.publication);\n}\n\nexport function validateCatalogDelta(\n value: unknown,\n): Result<CatalogDelta, CatalogDeltaValidationError> {\n if (!isRecord(value)) return catalogInvalid('delta');\n if (!Array.isArray(value.added)) return catalogInvalid('added');\n if (!Array.isArray(value.changed)) return catalogInvalid('changed');\n if (!Array.isArray(value.removed)) return catalogInvalid('removed');\n if (!value.added.every(isCatalogEntry)) return catalogInvalid('added.entry');\n if (!value.changed.every(isCatalogEntry)) return catalogInvalid('changed.entry');\n if (!value.removed.every((guid): guid is string => typeof guid === 'string' && guid.length > 0)) {\n return catalogInvalid('removed.guid');\n }\n const generation = value.generation;\n if (\n (value.scopeId !== undefined && !isNonEmptyString(value.scopeId)) ||\n (generation !== undefined &&\n (typeof generation !== 'number' || !Number.isSafeInteger(generation) || generation < 1))\n ) {\n return catalogInvalid('scope');\n }\n if ((value.scopeId === undefined) !== (value.generation === undefined)) {\n return catalogInvalid('scope.generation');\n }\n if (\n value.authority !== undefined &&\n value.authority !== 'authoritative' &&\n value.authority !== 'degraded'\n ) {\n return catalogInvalid('authority');\n }\n if (\n value.diagnostics !== undefined &&\n (!Array.isArray(value.diagnostics) || !value.diagnostics.every(isDiagnostic))\n ) {\n return catalogInvalid('diagnostics');\n }\n if (value.revisions !== undefined && !isRevisionWindow(value.revisions)) {\n return catalogInvalid('revisions');\n }\n if (\n value.topology !== undefined &&\n (!Array.isArray(value.topology) || !value.topology.every(isTopologyDiff))\n ) {\n return catalogInvalid('topology');\n }\n const identityKeys = [...value.added, ...value.changed].map((entry) => entry.guid.toLowerCase());\n if (new Set(identityKeys).size !== identityKeys.length) return catalogInvalid('duplicate.guid');\n const removedKeys = value.removed.map((guid) => guid.toLowerCase());\n if (new Set(removedKeys).size !== removedKeys.length) return catalogInvalid('duplicate.removed');\n if (value.authority === 'degraded' && identityKeys.length > 0) {\n return catalogInvalid('degraded.identity');\n }\n return ok(value as unknown as CatalogDelta);\n}\n\nfunction canonicalCatalogValue(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonicalCatalogValue).join(',')}]`;\n if (isRecord(value)) {\n return `{${Object.entries(value)\n .sort(([left], [right]) => left.localeCompare(right))\n .map(([key, child]) => `${JSON.stringify(key)}:${canonicalCatalogValue(child)}`)\n .join(',')}}`;\n }\n return JSON.stringify(value) ?? 'null';\n}\n\nfunction digestPart(value: string, seed: bigint): string {\n let hash = seed;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= BigInt(value.charCodeAt(index));\n hash = BigInt.asUintN(64, hash * 0x100000001b3n);\n }\n return hash.toString(16).padStart(16, '0');\n}\n\nconst CATALOG_DIGEST_SEEDS = [\n 0xcbf29ce484222325n,\n 0x84222325cbf29ce4n,\n 0x9e3779b185ebca87n,\n 0x517cc1b727220a95n,\n];\n\nfunction catalogDigest(value: unknown): string {\n const canonical = canonicalCatalogValue(value);\n return `sha256:${CATALOG_DIGEST_SEEDS.map((seed) => digestPart(canonical, seed)).join('')}`;\n}\n\n/** Canonical semantic identity for one Catalog row, independent of object key order. */\nexport function catalogEntryDigest(entry: CatalogEntry): string {\n return catalogDigest({ ...entry, guid: entry.guid.toLowerCase() });\n}\n\nexport function catalogDeltaDigest(delta: CatalogDelta): string {\n return catalogDigest({\n ...delta,\n added: [...delta.added].sort((left, right) => left.guid.localeCompare(right.guid)),\n changed: [...delta.changed].sort((left, right) => left.guid.localeCompare(right.guid)),\n removed: [...delta.removed].sort(),\n });\n}\n\nexport type PackIndexEntry = CatalogEntry;\n","import type { AssetStageErrorBase } from './asset-errors.js';\nimport type { SourceOverrideErrorCode, SourceOverrideMap } from './asset-producer.js';\nimport type { PackIndexEntry } from './catalog.js';\nimport type { Asset, AssetCodec, AssetRef, ImageError, TextureAsset } from './index.js';\n\nexport type ImportStageContractError = AssetStageErrorBase<'import', 'import-failed'>;\n\n// === Import contract SSOT (feat-20260603-asset-import-loader-injection M2 / w12) ===\n//\n// Decision anchors:\n// - requirements AC-07 (Importer dispatched by `meta.importer` string key) +\n// AC-08 (`importer-not-registered` fail-fast) + AC-09 (GUID import-stable\n// iron law: `guid-mismatch` / `import-produced-no-assets`) + AC-10\n// (`ImportErrorCode` is a closed union with exhaustive switch without default)\n// - plan-strategy D-6 (error model add-only: `ImportErrorCode` is an\n// independent closed union, not folded into `AssetErrorCode`) + D-1\n// (`ImporterRegistry` register/get/fail-fast mirrors LoaderRegistry) + D-4\n// (import runner skips the reserved `importer: 'shader'` key)\n// - research Finding 8 (`ImportTransport` interface slot; HTTP adapter is\n// OOS-2, landed in M4) + Finding 9 (`PackError` four-field shape is the\n// structural template)\n// - charter P3 (structured failure: `.code` / `.expected` / `.hint` /\n// `.detail`; AI users consume via property access, never `.message`\n// parsing) + P4 (consistent abstraction, structurally parallel to\n// `PackError` / `AssetError`)\n//\n// The import side is the build-time half of the import/load split. An\n// `Importer` turns an external source (a `.gltf` / `.png` / `.ttf` on disk)\n// plus its `*.meta.json` GUID declarations into in-memory `ImportedAsset[]`\n// (internal `Asset` PODs stamped with the meta-declared GUIDs). The import\n// runner then materializes those into the DDC (`.pack.json` / `.bin`). The\n// `Importer` itself stays pure of disk write + GUID minting — it consumes the\n// meta-declared GUIDs (GUID import-stable iron law) and emits PODs only.\n\n/**\n * Closed `ImportErrorCode` union for build-time import failures.\n * Used exclusively by the build-time `@forgeax/engine-import` runner +\n * `ImporterRegistry` fail-fast chain. Domain-separated from the runtime\n * `AssetErrorCode` (the typed `AssetRegistry.load` surface) and the disk-scanner\n * `PackErrorCode` — disjoint lifecycle phases. Counts evolve; see\n * AGENTS.md §Error model for the live roster.\n *\n * Exhaustive `switch (err.code)` needs no `default:` — TypeScript guards\n * union completeness at compile time (charter P2 machine-readable union >\n * prose + P3 explicit failure).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'importer-not-registered'` | the import runner read `meta.importer` but the injected `ImporterRegistry` has no importer for that key; `.detail.importer` is the missing key and `.detail.registeredImporters` lists the keys currently wired (charter P3 — AI users read `.detail.registeredImporters` to know what to inject). |\n * | `'source-read-failed'` | the source file referenced by `meta.source` could not be read (missing / unreadable); `.detail.source` is the path and `.detail.reason` the underlying error string. |\n * | `'import-produced-no-assets'` | the importer returned an empty `ImportedAsset[]`, or omitted a GUID that `meta.subAssets[]` declared (the produced GUID set is not a superset of the declared set); `.detail.missingGuids` lists the declared GUIDs the importer failed to produce. |\n * | `'guid-mismatch'` | the importer produced a GUID that `meta.subAssets[]` never declared (violates the GUID import-stable iron law); `.detail.unexpectedGuids` lists the produced GUIDs absent from the declared set. |\n * | `'import-internal-error'` | the importer failed at runtime. Two sub-cases ride `.detail`: a build-time module-LOAD failure surfaces `.detail.loadError`, while a conversion THROW surfaces `.detail.reason`. |\n * | `'source-validation-failed'` | the source was readable but failed an authoring rule; `.detail.diagnostics` contains source-located, machine-readable findings. |\n */\nexport type ImportErrorCode =\n | SourceOverrideErrorCode\n | 'importer-not-registered'\n | 'source-read-failed'\n | 'import-produced-no-assets'\n | 'guid-mismatch'\n | 'mesh-material-slot-topology-change'\n | 'import-internal-error'\n | 'source-validation-failed';\n\n/** A source range shared by all import diagnostics. */\nexport interface ImportSourceRange {\n readonly start: number;\n readonly end: number;\n readonly line: number;\n readonly column: number;\n}\n\n/** A related source location for a cross-file import diagnostic. */\nexport interface ImportDiagnosticLocation {\n readonly sourcePath: string;\n readonly sourceRange: ImportSourceRange;\n}\n\n/** Machine-readable provenance for one blocking or quality import finding. */\nexport interface ImportDiagnostic {\n readonly code: string;\n readonly severity: 'error' | 'warning';\n readonly sourcePath: string;\n readonly sourceRange: ImportSourceRange;\n readonly rule: string;\n readonly expected: string;\n readonly actual: string;\n readonly hint: string;\n readonly relatedLocations?: readonly ImportDiagnosticLocation[];\n}\n\n/**\n * Discriminated detail union for {@link ImportError} — narrowed per\n * `ImportError.code`. AI users access `err.detail.<field>` directly after\n * `switch (err.code)` narrows the variant. Structurally parallel to\n * `PackErrorDetail` (the `code` field is intentionally absent from each\n * variant; identify via the top-level `ImportError.code`).\n */\nexport type ImportErrorDetail =\n | {\n /** The `meta.importer` key with no registered importer. */\n readonly importer: string;\n /** The importer keys currently wired into the registry (insertion order). */\n readonly registeredImporters: readonly string[];\n }\n | {\n /** The `meta.source` path that could not be read. */\n readonly source: string;\n /** The underlying read error message. */\n readonly reason: string;\n }\n | {\n /** Declared sub-asset GUIDs the importer failed to produce (empty when the importer produced nothing at all). */\n readonly missingGuids: readonly string[];\n }\n | {\n /** Produced GUIDs absent from the `meta.subAssets[]` declared set. */\n readonly unexpectedGuids: readonly string[];\n }\n | {\n /** The original thrown error message (importer loaded but its conversion threw). */\n readonly reason: string;\n }\n | {\n /**\n * The module-load failure message (feat-20260629 D-5): the importer\n * module / native addon could not be loaded at build time (e.g.\n * module-not-found, native-addon-not-built). Distinguishes a LOAD\n * failure from a conversion THROW (`reason`) under the same\n * `import-internal-error` code without growing the closed ImportErrorCode\n * union. AI users branch on `'loadError' in err.detail`.\n */\n readonly loadError: string;\n }\n | {\n /** Source-located authoring findings retained across the import boundary. */\n readonly diagnostics: readonly ImportDiagnostic[];\n }\n | {\n /** Source override identity that failed Meta topology validation. */\n readonly sourceKey?: string;\n readonly declaredSourceKeys: readonly string[];\n readonly reason?: string;\n }\n | {\n readonly meshGuid: string;\n readonly meshSourceKey?: string;\n readonly previousIndices: readonly number[];\n readonly nextIndices: readonly number[];\n };\n\n/**\n * Structured import error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `PackError` / `AssetError`\n * (charter P4 consistent abstraction). `.detail` is narrowed per `.code` via\n * {@link ImportErrorDetail}.\n *\n * AI users consume the structured surface via property access:\n * `switch (err.code) { case 'guid-mismatch': ... err.detail.unexpectedGuids ... }`\n * — never by parsing `.message` (charter P3 red line).\n */\nexport class ImportError extends Error {\n readonly code: ImportErrorCode;\n readonly expected: string;\n readonly actual?: string;\n readonly hint: string;\n readonly detail: ImportErrorDetail;\n\n constructor(args: {\n code: ImportErrorCode;\n expected: string;\n actual?: string;\n hint: string;\n detail: ImportErrorDetail;\n }) {\n super(`[ImportError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'ImportError';\n this.code = args.code;\n this.expected = args.expected;\n if (args.actual !== undefined) this.actual = args.actual;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT. `Record<ImportErrorCode, string>`\n * makes a new closed-union member a compile-time error here as well\n * (reinforces charter P3 explicit failure). Consumed by the import runner +\n * tests so the producer and the fixtures share one source of truth.\n */\nexport const IMPORT_ERROR_HINTS: Readonly<Record<ImportErrorCode, string>> = {\n 'importer-not-registered':\n 'no importer registered for this meta.importer key; register one via importers.register(importer) (the importer carries its own key, e.g. gltfImporter / imageImporter); err.detail.registeredImporters lists the keys currently wired',\n 'source-read-failed':\n 'the file at meta.source could not be read; check the path is correct relative to the sidecar and the process has read access',\n 'import-produced-no-assets':\n 'the importer produced no assets, or omitted a GUID that meta.subAssets[] declared; the produced GUID set must be a superset of the declared set (GUID import-stable iron law); err.detail.missingGuids lists the declared GUIDs not produced',\n 'guid-mismatch':\n 'the importer produced a GUID that meta.subAssets[] never declared (violates the GUID import-stable iron law: GUIDs come from the external meta, never minted by the importer); err.detail.unexpectedGuids lists the offending GUIDs',\n 'mesh-material-slot-topology-change':\n 'the importer could not match previous and current Mesh material slots without ambiguity; name source materials uniquely or repair their stable sourceKey values before reimport',\n 'import-internal-error':\n 'the importer failed at runtime; branch on err.detail: a conversion THROW carries err.detail.reason (the loaded importer threw while converting the source — an importer bug, not a meta / source problem), while a build-time module-LOAD failure carries err.detail.loadError (the host importer module / native addon could not be imported)',\n 'source-validation-failed':\n 'the source violates an import authoring rule; inspect err.detail.diagnostics fields (code, sourcePath, sourceRange, rule, expected, actual, hint, and relatedLocations) and fix the referenced source',\n 'unknown-source-key':\n 'sourceOverrides contains a key absent from meta.subAssets[]; refresh the producer topology and use one of err.detail.declaredSourceKeys',\n 'duplicate-source-key':\n 'the producer declared the same sourceKey more than once; repair the Meta topology before importing',\n 'invalid-source-overrides':\n 'sourceOverrides must be an object keyed by producer-owned sourceKey values',\n 'invalid-source-override-payload':\n 'each sourceOverrides value must be a producer-owned object validated by the importer',\n};\n\n/**\n * One asset produced by an {@link Importer}: the meta-declared `guid`, the\n * in-memory `Asset` POD, and its outbound GUID cross-references (`refs`). The\n * import runner folds these into the DDC `.pack.json` `assets[]` rows (one\n * `ImportedAsset` -> one `{ guid, kind, payload, refs }` row).\n *\n * The `guid` always comes from `meta.subAssets[].guid` (GUID import-stable\n * iron law) — the importer never mints it; it reads the declared GUID off the\n * meta and stamps it here. `kind` mirrors the `Asset.kind` discriminant so the\n * DDC row and the runtime loader dispatch on the same string.\n */\nexport interface ImportedAsset<P = Asset> {\n readonly guid: string;\n readonly kind: string;\n readonly name?: string;\n readonly payload: P;\n readonly refs: readonly AssetRef[];\n readonly artifacts: Readonly<Record<string, ImportedArtifactBody>>;\n}\n\n/**\n * One declared sub-asset entry the import runner hands to an\n * {@link Importer.import} call — the meta-declared `guid` + its `sourceIndex`\n * + `kind`. Mirrors the `meta.subAssets[]` rows so the importer can map a\n * source object index to the GUID it must stamp (GUID import-stable iron law).\n */\nexport interface ImportSubAsset {\n readonly guid: string;\n readonly sourceIndex: number;\n /** Producer-owned semantic identity used to look up sourceOverrides. */\n readonly sourceKey?: string;\n readonly kind: string;\n}\n\n/**\n * Capabilities + declarations the import runner wires into an\n * {@link Importer.import} call. The importer reads the source bytes via\n * `readSource`, the GUID declarations via `subAssets`, and the free-form\n * importer settings via `importSettings`. It stays pure of disk write +\n * registry bookkeeping (pipeline isolation, architecture-principles #4).\n *\n * - `source` — the `meta.source` path (relative to the sidecar), for\n * diagnostics + the importer's own external-resource resolution base.\n * - `readSource()` — fetch the raw source bytes (the runner has already\n * resolved the path); a structured failure here surfaces as\n * `source-read-failed`.\n * - `subAssets` — the `meta.subAssets[]` GUID declarations the importer must\n * honour (GUID import-stable iron law).\n * - `importSettings` — free-form importer settings copied verbatim from the\n * sidecar.\n * - `readSibling(uri)` — fetch raw bytes of a file co-located with the\n * primary source (e.g. an `.gltf` referencing an external `.bin` /\n * `.png` via relative URI). Failures surface as `source-read-failed`\n * (C-6 — no specialised error code; the URI is forensic detail). Used\n * by gltfImporter to resolve `images[].uri` external references at\n * import time.\n * - `decodeImage(bytes, mimeType, importSettings)` — decode raw image\n * bytes (PNG / JPEG) into a `TextureAsset` POD plus a `bytes` copy\n * suitable for `<guid>.bin` emission. When the host applies an offline\n * delivery codec, it also returns the artifact media type and codec so\n * gltfImporter can preserve those facts in the Pack v2 envelope. The seam\n * keeps gltfImporter\n * out of `@forgeax/engine-image` (D-1: zero static `from\n * '@forgeax/engine-image'` edge in `packages/gltf/src`). The\n * concrete implementation (parseImage + format derivation) lives\n * behind `@forgeax/engine-image/image-importer`; the build-time\n * orchestrator (vite-plugin-pack / cli-gltf / tests) binds the\n * callback when constructing the runner's `ImportRunnerFs`.\n */\nexport interface ImportContext {\n readonly source: string;\n readSource(): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: unknown }\n >;\n readSibling(\n uri: string,\n ): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: ImportError }\n >;\n decodeImage(\n bytes: Uint8Array,\n mimeType: 'image/png' | 'image/jpeg' | 'image/x-tga',\n importSettings: Readonly<Record<string, unknown>>,\n ): Promise<\n | {\n readonly ok: true;\n readonly value: {\n readonly texture: TextureAsset;\n readonly bytes: Uint8Array;\n readonly mediaType?: string;\n readonly assetCodec?: AssetCodec;\n };\n }\n | { readonly ok: false; readonly error: ImageError }\n >;\n readonly subAssets: readonly ImportSubAsset[];\n readonly importSettings: Readonly<Record<string, unknown>>;\n /** Optional Meta author facts passed through without importer-kind branching. */\n readonly sourceOverrides?: SourceOverrideMap;\n}\n\n/**\n * Build-time importer injected into the `ImporterRegistry`. One importer per\n * `meta.importer` key; the import runner dispatches on the key.\n *\n * `import` is pure of disk write + GUID minting: it reads the source via\n * `ctx.readSource()`, honours the `ctx.subAssets[]` GUID declarations, and\n * returns the produced `ImportedAsset[]`. The runner validates the produced\n * GUID set against the declared set (GUID import-stable iron law) and writes\n * the DDC. A thrown error is wrapped by the runner into\n * `import-internal-error` (charter P3) — importers may throw, but should\n * prefer returning a partial / empty result so the runner can attribute the\n * failure precisely.\n */\nexport interface ImportProductFinalizeArtifact {\n readonly path: string;\n readonly mimeType: string;\n readonly bytes: Uint8Array;\n}\n\nexport interface ImportProductFinalizeOptions {\n readonly artifactUrl: (artifact: ImportProductFinalizeArtifact) => string;\n}\n\nexport type ImportProductFinalizeResult =\n | {\n readonly ok: true;\n readonly value: {\n readonly asset: unknown;\n readonly artifacts: readonly { readonly path: string; readonly mimeType: string }[];\n };\n }\n | {\n readonly ok: false;\n readonly error: {\n readonly code: string;\n readonly expected: string;\n readonly hint: string;\n readonly detail: unknown;\n };\n };\n\n/** Optional producer capability exposed to the generic import runner. */\nexport interface ImporterCapabilities {\n readonly decodeImage?: ImportContext['decodeImage'];\n /** Producer-owned Catalog visibility for declarations before materialization. */\n readonly catalog?: {\n readonly publish?: (input: {\n readonly importSettings: Readonly<Record<string, unknown>>;\n readonly subAssets: readonly ImportSubAsset[];\n }) => boolean;\n };\n}\n\nexport interface Importer {\n readonly key: string;\n // biome-ignore lint/suspicious/noExplicitAny: pending downstream importer migration keeps old consumers source-compatible\n import(ctx: ImportContext): Promise<any> | any;\n readonly capabilities?: ImporterCapabilities;\n /** Optional owner projection for transport artifacts produced by this importer. */\n finalize?: (\n product: ImportProduct<unknown>,\n options: ImportProductFinalizeOptions,\n ) => ImportProductFinalizeResult;\n}\n/**\n * Interface slot for the M4 lazy-import transport (OOS-2). A runtime\n * `ImportTransport` fetches a missing DDC artefact on demand (the shipped form\n * never falls back to a runtime import; see `AssetErrorCode 'asset-not-imported'`).\n * Declared here as a contract seam only — the HTTP adapter lands in M4 w31;\n * M2 does not implement it (plan-strategy D-6 / research Finding 8).\n */\nexport interface ImportTransport {\n /**\n * Trigger an on-demand DDC import for a GUID at runtime. On success the\n * transport returns the freshly imported catalog rows for the GUID (and any\n * sub-asset siblings produced by the same import) so the caller patches just\n * those rows into its catalog cache -- per-asset incremental, never a\n * whole-catalog re-fetch (the four-verb redesign, 2026-06-06). `entries` may\n * be empty when the transport imported the artefact but does not surface the\n * rows; the caller then re-resolves the GUID from its (possibly stale) cache.\n * `ok: false` means the import did not produce an artefact and the caller\n * surfaces `asset-not-imported`.\n */\n fetchPack(\n guid: string,\n scope?: Pick<\n import('./runtime-scope.js').RuntimeAssetBinding,\n 'scopeId' | 'generation' | 'status'\n >,\n ): Promise<\n { readonly ok: true; readonly entries?: readonly PackIndexEntry[] } | { readonly ok: false }\n >;\n}\n\n/** A normalized filesystem path observed by an importer read attempt. */\nexport type SourceDependency = string;\n\n/** Logical artifact content emitted by one imported asset. */\nexport interface ImportedArtifactBody {\n readonly mediaType: string;\n readonly assetCodec?: AssetCodec;\n readonly bytes: Uint8Array;\n}\n\n/** Generic import output shared by every importer and transport. */\nexport interface ImportProduct<P = Asset> {\n readonly assets: readonly ImportedAsset<P>[];\n readonly sourceDependencies: readonly SourceDependency[];\n}\n\n/** Structured result returned by an importer. */\nexport type ImportResult<P = Asset> =\n | { readonly ok: true; readonly value: ImportProduct<P> }\n | { readonly ok: false; readonly error: ImportError };\n\n/* ImportTransport is intentionally kept with the build-time contract. */\n","// @forgeax/engine-types — POD types, union aliases, and cross-package primitives SSOT.\n//\n// Proposition: this package is the single source of truth for shared shapes that\n// must NOT diverge across @forgeax/engine-rhi / ecs / naga / image / gltf / console /\n// render-graph / shader / future renderer packages.\n//\n// Scope:\n// - POD types & union aliases — Asset / MaterialAsset / RenderQueue / PassKind /\n// FontAsset / RenderPipelineAsset / SceneAsset /\n// PackErrorCode / ImageErrorCode / AudioErrorCode / PhysicsErrorCode etc.\n// - Structured error classes — AssetError / FontError / TextError / AudioError /\n// PhysicsError (carry .code / .expected / .hint surface).\n// - Project-wide Result<T, E> + ok / err factories (tweak-20260612-result-into-types\n// consolidated 5 byte-aligned-by-prose copies into this single module).\n// - GPUFlagsConstant aliases for @webgpu/types numeric runtime constants — the\n// global objects (e.g. GPUBufferUsage.MAP_READ) are still consumed directly by\n// upstream callers; only the literal type aliases live here (decision S-6 /\n// research F-2 option (b)).\n//\n// Shape rules:\n// - math-free (no vec / mat / quat dependency).\n// - Single-source policy — fields already exported by @webgpu/types are re-exported\n// verbatim as one-line aliases; never duplicated here.\n//\n// Anchors: requirements §AC AC-01 + MVP-1.5; plan-strategy §2 S-6 + §7.6 propositions\n// 4 / 5; research §F-2 (`GPUFlagsConstant = number` alias).\n\n/// <reference types=\"@webgpu/types\" />\n\n// === Handle SSOT barrel re-export (feat-20260517-handle-type-unify M2 / D-2) ====\n//\n// `./handle` carries the unique double-axis Handle<T extends string, M> brand\n// + AssetTagMap + TagOf + 3 factories (toUnique / toShared / unwrapHandle).\n// The legacy 1-arg form that lived here at M1 has been physically deleted\n// (M2 t10) so the package surface exposes a single Handle shape (charter F1\n// single-entry indexability; AC-03 grep gate).\n\nimport type { AnimationTargetIdValue } from './animation-target';\nimport type { AudioClipAsset } from './asset.js';\nimport type { MaterialAsset } from './material/asset.js';\nimport type { ParticleEffectAsset } from './vfx';\n\nexport type { AnimationTargetIdValue } from './animation-target';\nexport * from './asset.js';\nexport * from './asset-errors.js';\nexport type {\n AssetArtifactReader,\n AssetDecoder,\n AssetDecoderContribution,\n AssetDecoderContributionRef,\n AssetDecoderInput,\n AssetDecoderLease,\n AssetDecoderResult,\n AssetKind,\n AssetKindPayload,\n AssetRegistryAction,\n AssetRuntimeApiGroups,\n BuiltinAssetKind,\n BuiltinAssetKindToken,\n BuiltinAssetPayload,\n} from './asset-runtime.js';\nexport * from './handle';\nexport * from './material/index.js';\n\n// === Result<T, E> SSOT (tweak-20260612-result-into-types) ======================\n//\n// Single physical source of `Result<T, E>` + `ok(...)` / `err(...)`. Replaces\n// the prior dual copies in packages/rhi/src/errors.ts + packages/ecs/src/result.ts\n// (those copies were \"byte-for-byte aligned\" by prose, not by mechanism).\n// AI users import the binary success/failure carrier via `@forgeax/engine-types`\n// (or via the rhi/ecs package barrels which re-export this same module).\nexport * from './result';\nexport * from './runtime-scope';\nexport * from './vfx';\n\n// === Sub-asset POD SSOT (feat-20260615-fbx-importer-via-sdk M1 / t9) ===========\n//\n// Importer-independent pure-data IR types shared across glTF / FBX / future\n// format importers. These are pre-kind, pre-guid data carriers — each importer\n// writes these Pods from its format-specific JSON POD, and `to-asset-pack`\n// promotes them to registry-ready Asset handles.\n//\n// Design axioms:\n// - SSOT (architecture-principles #1): defined once in @forgeax/engine-types,\n// consumed via import by gltf / fbx / future importer packages.\n// - Derive, don't duplicate (#2): gltf/fbx drop their local MeshIr/MeshRecord\n// and import MeshPod — no per-package copy.\n// - No format prefix (plan-strategy section 8): Pod types are named after the\n// *asset kind* they represent, not the source format. AI users write code\n// that reads MeshPod regardless of whether the source was FBX or glTF.\n// - Pre-kind data (charter P4): Pods carry raw geometric/material data without\n// `kind` discriminant fields. The importer bridge layer adds `kind` when\n// converting Pod -> Asset handle.\n//\n// Pod roster (AC-01..AC-07):\n// MeshPod — vertices/indices/attributes/submeshes\n// MaterialPod — PBR parameter values (baseColor/metallic/roughness)\n// ScenePod — entity hierarchy + mount points\n// TexturePod — external file path (cross-platform normalized)\n// SkeletonPod — joint count + inverse bind matrices\n// SkinPod — skeleton reference + joint paths\n// AnimationClipPod — duration + channels + samplers\n\n// === AC-01: MeshPod — pure geometric data ===\n\n/** Per-submesh descriptor within a MeshPod. */\nexport interface MeshSubmeshPod {\n /** Vertex count for this submesh (draw count when non-indexed). */\n readonly vertexCount: number;\n /** Index count when indexed; 0 for non-indexed geometry. */\n readonly indexCount: number;\n /** Byte offset into the shared indices buffer (0-based). */\n readonly indexOffset: number;\n /** Material binding index into the parent document's materials array. */\n readonly materialIndex: number | null;\n /** Primitive topology. */\n readonly topology: 'triangle-list' | 'line-list' | 'line-strip' | 'point-list';\n}\n\n/** MeshPod: pre-kind geometric data IR shared across importers. */\nexport interface MeshPod {\n /** Optional debug name from source document. */\n readonly name?: string;\n /** Packed vertex positions (Float32Array, 3 floats per vertex). */\n readonly vertices: Float32Array;\n /** Packed triangle indices (Uint16Array or Uint32Array). Absent when non-indexed. */\n readonly indices?: Uint16Array | Uint32Array;\n /** Per-vertex attributes keyed by semantic (POSITION/NORMAL/TEXCOORD_0/JOINTS_0/WEIGHTS_0). */\n readonly attributes: Record<string, Float32Array | Uint16Array | Uint32Array>;\n /** Optional additive morph deltas, one entry per target. */\n readonly morphTargets?: readonly MorphTarget[];\n /** Optional default morph weights, one value per target. */\n readonly morphWeights?: Float32Array;\n /** Per-submesh descriptors (>=1). */\n readonly submeshes: readonly MeshSubmeshPod[];\n /** Source mesh index within the original document (for diagnostic mapping). */\n readonly sourceIndex: number;\n}\n\n// === AC-02: MaterialPod — PBR parameter values ===\n\n/** MaterialPod: pre-kind PBR material data IR shared across importers. */\nexport const MATERIAL_TEXTURE_SLOTS = [\n 'baseColorTexture',\n 'normalTexture',\n 'specularTintTexture',\n 'metallicRoughnessTexture',\n 'emissiveTexture',\n 'occlusionTexture',\n] as const;\n\nexport type MaterialTextureSlot = (typeof MATERIAL_TEXTURE_SLOTS)[number];\n\nexport interface MaterialTextureBindingPod {\n /** Standard material value slot receiving the texture reference. */\n readonly slot: MaterialTextureSlot;\n /** Index into the parent document's textures array. */\n readonly textureIndex: number;\n /** Optional source UV set. */\n readonly texCoord?: number;\n}\n\nexport interface MaterialPod {\n /** Optional debug name from source document. */\n readonly name?: string;\n /** RGBA base color factor (linear space). */\n readonly baseColorFactor: readonly [number, number, number, number];\n /** Metallic factor (0..1). */\n readonly metallicFactor: number;\n /** Roughness factor (0..1). */\n readonly roughnessFactor: number;\n /** Index into the parent document's textures array for base color map. */\n readonly baseColorTextureIndex?: number;\n /** Index for metallic-roughness packed texture. */\n readonly metallicRoughnessTextureIndex?: number;\n /** Index for normal map. */\n readonly normalTextureIndex?: number;\n /** Index for occlusion map. */\n readonly occlusionTextureIndex?: number;\n /** Index for emissive map. */\n readonly emissiveTextureIndex?: number;\n /** Index for a specular tint map. */\n readonly specularTintTextureIndex?: number;\n /** Engine-owned semantic texture bindings from the producer. */\n readonly textureBindings?: readonly MaterialTextureBindingPod[];\n}\n\n// === AC-03: ScenePod — entity hierarchy ===\n\n/** A single entity node within a ScenePod hierarchy. */\nexport interface SceneEntityPod {\n /** Entity name (for Name component attachment). */\n readonly name: string;\n /** Decomposed local transform (TRS). */\n readonly transform: {\n readonly translation: readonly [number, number, number];\n readonly rotation: readonly [number, number, number, number];\n readonly scale: readonly [number, number, number];\n };\n /** Index into the parent document's meshes array. Null when not a mesh node. */\n readonly meshIndex: number | null;\n /** Children entity indices in the flattened entities array. */\n readonly children: readonly number[];\n}\n\n/** ScenePod: entity hierarchy IR shared across importers. */\nexport interface ScenePod {\n /** Optional scene name. */\n readonly name?: string;\n /** Flattened entity list (topological order, parents before children). */\n readonly entities: readonly SceneEntityPod[];\n /** Index of the default/root scene entity. */\n readonly rootEntityIndex: number;\n}\n\n// === AC-04: TexturePod — external file path ===\n\n/** TexturePod: external texture reference IR shared across importers. */\nexport interface TexturePod {\n /** Optional texture name. */\n readonly name?: string;\n /** Filesystem path relative to the source document, with '/' separators. */\n readonly filePath: string;\n /** The producer-declared relative path before host resolution. */\n readonly relativeFilePath?: string;\n /** Parse-scope absolute hint; never persist this into project metadata. */\n readonly absoluteFilePath?: string;\n /** Embedded encoded image bytes, when the FBX contains the texture payload. */\n readonly embeddedBytes?: Uint8Array;\n /** Producer texture kind; only file/embedded textures are importable. */\n readonly type?: 'file' | 'layered' | 'procedural' | 'shader' | 'unknown';\n /** Source texture index within the original document. */\n readonly sourceIndex: number;\n}\n\n// === AC-05: SkeletonPod — joint hierarchy ===\n\n/** SkeletonPod: skeleton joint data IR shared across importers. */\nexport interface SkeletonPod {\n /** Number of joints. */\n readonly jointCount: number;\n /** Inverse bind matrices, Float32Array of length jointCount * 16. */\n readonly inverseBindMatrices: Float32Array;\n /** Per-joint name path from scene root (parallel to joints array). */\n readonly jointPaths: readonly string[];\n}\n\n// === AC-06: SkinPod — vertex skinning data ===\n\n/** Per-vertex joint influence descriptor. */\nexport interface SkinVertexInfluencePod {\n /** 4 joint indices (Uint16Array), always padded to 4 entries. */\n readonly jointIndices: Uint16Array;\n /** 4 joint weights (Float32Array), always padded to 4 entries. */\n readonly jointWeights: Float32Array;\n}\n\n/** SkinPod: vertex skinning data IR shared across importers. */\nexport interface SkinPod {\n /** GUID-like identifier for the associated SkeletonAsset (resolved at bridge time). */\n readonly skeletonGuid: string;\n /** Joint name paths (same as SkeletonPod.jointPaths for cross-reference). */\n readonly jointPaths: readonly string[];\n /** Number of influenced vertices. */\n readonly vertexCount: number;\n /** Per-vertex joint influences (4 joints per vertex). */\n readonly influences: readonly SkinVertexInfluencePod[];\n}\n\n// === AC-07: AnimationClipPod — keyframe animation ===\n\n/** Animation sampler (keyframe data for one property). */\nexport interface AnimationSamplerPod {\n /** Keyframe timestamps (ascending, seconds). */\n readonly input: Float32Array;\n /** Keyframe values (packed per-element stride). */\n readonly output: Float32Array;\n /** Interpolation mode. */\n readonly interpolation: 'LINEAR' | 'STEP';\n}\n\n/** Animation channel (one target-property pair). */\nexport interface AnimationChannelPod {\n /** Stable animation target identity. */\n readonly targetId: AnimationTargetIdValue;\n /** Target property: 'translation' | 'rotation' | 'scale' | 'weights'. */\n readonly property: 'translation' | 'rotation' | 'scale' | 'weights';\n /** Sampler driving this channel. */\n readonly sampler: AnimationSamplerPod;\n}\n\n/** AnimationClipPod: keyframe animation clip IR shared across importers. */\nexport interface AnimationClipPod {\n /** Optional clip name. */\n readonly name?: string;\n /** Clip duration in seconds (max sampler.input[last]). */\n readonly duration: number;\n /** Per-animation-target-property channels. */\n readonly channels: readonly AnimationChannelPod[];\n}\n\n// === Asset system v1 SSOT (feat-20260511-asset-system-v1) ======================\n//\n// Decision anchors:\n// - requirements §G7 + §2 row 8 + AC-09 / AC-15 / AC-21 (4-variant Asset\n// discriminated union, 14-key VertexAttributeMap closed set,\n// AssetErrorCode 4-member closed union elevated to TS alias)\n// - plan-strategy §2 D-P1 (@forgeax/engine-types single-file SSOT for\n// Asset union + AssetErrorCode; 4-member AssetErrorCode independent from\n// RhiErrorCode, AI users discover through one-line import)\n// - plan-strategy §7.2 (lowercase key alignment with Three.js r184\n// BufferGeometry mental migration; D-P5 preserves segments 6 params)\n// - plan-strategy §7.3 (AssetError .hint strings per error code, verbatim)\n// - charter proposition 1 (single-entry IDE autocomplete via\n// `@forgeax/engine-types`) + proposition 3 (machine-readable union >\n// prose) + proposition 4 (explicit failure - exhaustive switch needs no\n// default fallback) + proposition 5 (consistent abstraction - structurally\n// parallel to RhiError / InspectorError / MetricError 4-field surface)\n// - architecture-principles #1 SSOT (4 literals + shape live here once;\n// @forgeax/engine-runtime AssetRegistry / tests / AGENTS.md Error model\n// table all reference this module)\n\n/**\n * Mesh asset POD shape aligned with Three.js r184 BufferGeometry mental\n * model (plan-strategy §7.2 naming convention). `vertices` is the interleaved\n * or primary position buffer; `indices` narrows to `Uint16Array | Uint32Array`\n * per WebGPU spec index format; `attributes` is the VertexAttributeMap\n * lowercase-key closed set.\n *\n * Canonical lowercase keys include position, normal, uv, tangent, skinIndex,\n * skinWeight, uv1..uv7 and optional linear RGBA color.\n *\n * Designed for M3 GLTF loader single-layer mapping\n * (`POSITION -> position` / `TEXCOORD_0 -> uv` etc.) without runtime rename.\n */\nexport interface MeshAsset {\n readonly kind: 'mesh';\n readonly vertices: Float32Array;\n /**\n * Index buffer. Optional: vertex-only meshes (point-list / line-list with no\n * shared vertices) omit it, and the engine takes a non-indexed draw path\n * (`pass.draw(vertexCount)` instead of `pass.drawIndexed`). When present the\n * indexed path is byte-for-byte unchanged.\n */\n readonly indices?: Uint16Array | Uint32Array;\n readonly attributes: VertexAttributeMap;\n /**\n * Axis-aligned bounding box in local space: 6 floats [minX, minY, minZ, maxX, maxY, maxZ].\n *\n * Producer obligation: every MeshAsset MUST carry a computed `aabb` from its\n * position attribute. Built-in producers (glTF, FBX, geometry factories)\n * fill it automatically via `box3.fromPositions` -- the single\n * authoritative implementation in @forgeax/engine-math.\n *\n * When `aabb` is `undefined`, the pick() broad-phase and frustum culling\n * silently skip the mesh -- pick() returns `undefined` for every ray query\n * against it with no diagnostic signal. AI users hand-writing MeshAsset\n * must self-check that `aabb` is populated.\n *\n * Empty / degenerate position input (0 vertices) produces an\n * inverted-infinity empty box (min = +Infinity, max = -Infinity).\n * Consumers reject this for picking (min.x > max.x) and may skip it\n * for culling.\n *\n * The bare Float32Array keeps engine-types math-free (no Box3 branded\n * type dependency). Consumers narrow to the math-layer Box3 via\n * `as Box3Like`.\n */\n readonly aabb?: Float32Array;\n /**\n * Submeshes partition the index/vertex range into independent draw calls,\n * each with its own topology (one of the 5 WebGPU primitives:\n * 'point-list' | 'line-list' | 'line-strip' | 'triangle-list' | 'triangle-strip').\n *\n * Every mesh must declare at least one submesh. The engine draws one\n * `drawIndexed` (or `draw` for vertex-only) per submesh entry, and pairs\n * them with `MeshRenderer.materials[]` by index position.\n *\n * Must be non-empty: an empty array triggers a `mesh-asset-submeshes-empty`\n * AssetError at register-time (fail-fast, charter P3 explicit failure).\n */\n readonly submeshes: readonly Submesh[];\n /** Stable, mesh-owned material entry points shared by every instance. */\n readonly materialSlots: readonly MeshMaterialSlot[];\n /** Target-major dense morph deltas. */\n readonly morphTargets?: readonly MorphTarget[];\n /** Authored default weights, one value per morph target when present. */\n readonly morphWeights?: Float32Array;\n}\n\nexport interface MorphTarget {\n readonly position?: Float32Array;\n readonly normal?: Float32Array;\n readonly tangent?: Float32Array;\n}\n\n/** Mesh-owned default binding for one stable material slot. */\nexport interface MeshMaterialSlot {\n /** Unique, non-empty display/tooling name within the mesh. */\n readonly slotName: string;\n /** Producer-owned stable identity used to preserve slot indices on reimport. */\n readonly sourceKey?: string;\n /** Missing means the slot intentionally inherits the neutral engine material. */\n readonly defaultMaterial?: AssetGuid;\n}\n\n/** JSON-safe producer topology persisted beside an imported Mesh output. */\nexport interface MeshMaterialSlotTopologyEntry {\n readonly slotName: string;\n readonly sourceKey?: string;\n readonly defaultMaterialGuid?: string;\n /** Persisted removed-slot identity; never participates in cooked bindings. */\n readonly tombstone?: true;\n}\n\n/** Resolve the persisted source/authoring layers to the one runtime Mesh default. */\nexport function resolveMeshMaterialSlotDefaultGuid(\n slot: MeshMaterialSlotTopologyEntry,\n authoredDefaultMaterialGuid?: string | null,\n): string | undefined {\n if (authoredDefaultMaterialGuid !== undefined) {\n return authoredDefaultMaterialGuid === null ? undefined : authoredDefaultMaterialGuid;\n }\n return slot.defaultMaterialGuid;\n}\n\nexport interface MeshMaterialSlotTopologyChange {\n readonly code: 'mesh-material-slot-topology-change';\n readonly previousIndices: readonly number[];\n readonly nextIndices: readonly number[];\n readonly hint: string;\n}\n\nexport type MeshMaterialSlotReconcileResult =\n | {\n readonly ok: true;\n readonly slots: readonly MeshMaterialSlotTopologyEntry[];\n /** New source-order slot index -> stable persisted slot index. */\n readonly currentToStableSlot: readonly number[];\n }\n | { readonly ok: false; readonly error: MeshMaterialSlotTopologyChange };\n\n/**\n * Preserve positional renderer overrides across source reimport.\n *\n * Stable sourceKey wins, then a unique slotName. A single remaining pair is\n * the only unambiguous source-order fallback. Removed slots remain as\n * defaultless tombstones; new slots append, so an old index never silently\n * starts naming a different source material.\n */\nexport function reconcileMeshMaterialSlotTopology(\n current: readonly MeshMaterialSlotTopologyEntry[],\n previous: readonly MeshMaterialSlotTopologyEntry[] = [],\n): MeshMaterialSlotReconcileResult {\n if (previous.length === 0) {\n return { ok: true, slots: [...current], currentToStableSlot: current.map((_, index) => index) };\n }\n\n const stable: MeshMaterialSlotTopologyEntry[] = previous.map((slot) => ({\n slotName: slot.slotName,\n ...(slot.sourceKey === undefined ? {} : { sourceKey: slot.sourceKey }),\n tombstone: true,\n }));\n const mapping = new Array<number>(current.length).fill(-1);\n const usedPrevious = new Set<number>();\n\n const uniqueIndex = (\n slots: readonly MeshMaterialSlotTopologyEntry[],\n read: (slot: MeshMaterialSlotTopologyEntry) => string | undefined,\n ): Map<string, number> => {\n const first = new Map<string, number>();\n const duplicates = new Set<string>();\n slots.forEach((slot, index) => {\n const key = read(slot)?.trim();\n if (!key) return;\n if (first.has(key)) duplicates.add(key);\n else first.set(key, index);\n });\n for (const duplicate of duplicates) first.delete(duplicate);\n return first;\n };\n\n const match = (read: (slot: MeshMaterialSlotTopologyEntry) => string | undefined): void => {\n const oldByKey = uniqueIndex(previous, read);\n const nextByKey = uniqueIndex(current, read);\n for (const [key, nextIndex] of nextByKey) {\n if (mapping[nextIndex] !== -1) continue;\n const oldIndex = oldByKey.get(key);\n if (oldIndex === undefined || usedPrevious.has(oldIndex)) continue;\n mapping[nextIndex] = oldIndex;\n usedPrevious.add(oldIndex);\n }\n };\n\n match((slot) => slot.sourceKey);\n match((slot) => slot.slotName);\n\n const unmatchedCurrent = mapping\n .map((oldIndex, index) => (oldIndex === -1 ? index : -1))\n .filter((index) => index !== -1);\n const unmatchedPrevious = previous\n .map((_, index) => (usedPrevious.has(index) ? -1 : index))\n .filter((index) => index !== -1);\n if (\n unmatchedCurrent.length === 1 &&\n unmatchedPrevious.length === 1 &&\n current[unmatchedCurrent[0] as number]?.sourceKey === undefined &&\n previous[unmatchedPrevious[0] as number]?.sourceKey === undefined\n ) {\n mapping[unmatchedCurrent[0] as number] = unmatchedPrevious[0] as number;\n usedPrevious.add(unmatchedPrevious[0] as number);\n unmatchedCurrent.length = 0;\n unmatchedPrevious.length = 0;\n }\n const identityInsufficient =\n unmatchedCurrent.some((index) => current[index]?.sourceKey === undefined) &&\n unmatchedPrevious.some((index) => previous[index]?.sourceKey === undefined);\n if (unmatchedCurrent.length > 0 && unmatchedPrevious.length > 0 && identityInsufficient) {\n return {\n ok: false,\n error: {\n code: 'mesh-material-slot-topology-change',\n previousIndices: unmatchedPrevious,\n nextIndices: unmatchedCurrent,\n hint: 'name source materials uniquely or provide stable sourceKey values before reimport',\n },\n };\n }\n\n for (let currentIndex = 0; currentIndex < current.length; currentIndex++) {\n let stableIndex = mapping[currentIndex] as number;\n if (stableIndex === -1) {\n stableIndex = stable.length;\n mapping[currentIndex] = stableIndex;\n }\n stable[stableIndex] = current[currentIndex] as MeshMaterialSlotTopologyEntry;\n }\n return { ok: true, slots: stable, currentToStableSlot: mapping };\n}\n\nexport interface MeshMaterialOverrideMigrationContext {\n readonly meshGuid: string;\n readonly sceneGuid: string;\n readonly entityId: number;\n}\n\nexport interface MeshMaterialOverrideConflict extends MeshMaterialOverrideMigrationContext {\n readonly code: 'mesh-material-slot-override-conflict';\n readonly materialSlot: number;\n readonly submeshIndices: readonly number[];\n readonly overrideHandles?: readonly number[];\n readonly overrideGuids?: readonly string[];\n readonly hint: string;\n}\n\nexport type MeshMaterialOverrideMigrationResult<T extends number | string = number> =\n | { readonly ok: true; readonly overrides: readonly T[] }\n | { readonly ok: false; readonly error: MeshMaterialOverrideConflict };\n\n/**\n * Collapse legacy per-submesh overrides into v3 per-slot overrides.\n * Conflicting section overrides fail atomically with full dependency context.\n */\nexport function migrateLegacyMeshMaterialOverrides<T extends number | string>(\n legacyOverrides: readonly T[],\n submeshes: readonly Pick<Submesh, 'materialSlot'>[],\n materialSlotCount: number,\n context: MeshMaterialOverrideMigrationContext,\n): MeshMaterialOverrideMigrationResult<T> {\n const inherited = (typeof legacyOverrides[0] === 'string' ? '' : 0) as T;\n const overrides = new Array<T>(materialSlotCount).fill(inherited);\n const sectionsBySlot = new Map<number, number[]>();\n for (let submeshIndex = 0; submeshIndex < submeshes.length; submeshIndex++) {\n const materialSlot = submeshes[submeshIndex]?.materialSlot;\n if (materialSlot === undefined || materialSlot < 0 || materialSlot >= materialSlotCount)\n continue;\n const sections = sectionsBySlot.get(materialSlot) ?? [];\n sections.push(submeshIndex);\n sectionsBySlot.set(materialSlot, sections);\n }\n for (const [materialSlot, submeshIndices] of sectionsBySlot) {\n const handles: T[] = submeshIndices.map((index) => legacyOverrides[index] ?? inherited);\n const distinct = [...new Set(handles)];\n if (distinct.length > 1) {\n return {\n ok: false,\n error: {\n code: 'mesh-material-slot-override-conflict',\n ...context,\n materialSlot,\n submeshIndices,\n ...(typeof handles[0] === 'string'\n ? { overrideGuids: handles as string[] }\n : { overrideHandles: handles as number[] }),\n hint: 'split the source slot or choose one override explicitly before v2 to v3 recook',\n },\n };\n }\n overrides[materialSlot] = distinct[0] ?? inherited;\n }\n return { ok: true, overrides };\n}\n\n/**\n * Submesh partitions a mesh's index/vertex range into an independent draw\n * call with its own primitive topology.\n *\n * Every field is required -- there is no default topology; the caller\n * must state the intended primitive type explicitly (charter P3 explicit\n * failure: silent default would mask topology mistakes).\n *\n * Naming aligns with Unity `SubMeshDescriptor` (without firstVertex /\n * baseVertex / bounds which are out of scope per OOS-3/OOS-4).\n *\n * | field | description |\n * |:--|:--|\n * | `indexOffset` | Start offset into the parent mesh's index buffer (in elements, not bytes). For vertex-only (non-indexed) submeshes, set to 0. |\n * | `indexCount` | Number of indices consumed from the index buffer starting at `indexOffset`. For vertex-only submeshes, set to 0. |\n * | `vertexCount` | Number of vertices spanned by this submesh range (used for the non-indexed draw path and for index-range OOB validation). |\n * | `topology` | GPU primitive topology for this submesh (one of the 5 WebGPU primitives: 'point-list' \\| 'line-list' \\| 'line-strip' \\| 'triangle-list' \\| 'triangle-strip'). |\n */\nexport interface Submesh {\n readonly indexOffset: number;\n readonly indexCount: number;\n readonly vertexCount: number;\n readonly topology: PrimitiveTopology;\n /** Index into the owning MeshAsset.materialSlots table. */\n readonly materialSlot: number;\n}\n\n/**\n * Texture asset POD shape aligned with `@webgpu/types ^0.1.69`\n * `GPUTextureDescriptor` subset (plan-strategy D-P1; RHI form rule\n * \"spec-aligned\"). Carries the decoded pixel bytes ready for\n * `GPUQueue.writeTexture` / `copyExternalImageToTexture` upload.\n *\n * `format` is the `GPUTextureFormat` string-literal union; `data` holds the\n * CPU-side decoded pixels (tight-packed, srgb-premultiplied-alpha-false per\n * D-P9). Optional `mipLevelCount` / `sampleCount` default to 1 at upload\n * time; v1 registers only 2D textures (depth / 3D / cube array are future\n * spinoffs).\n *\n * feat-20260515-learn-render-getting-started M3 / T-M3-03 minor-add (Asset\n * closed-union member count unchanged at 5; plan-strategy section 2.5 D Open\n * Q-4 selection (c)):\n * - `colorSpace: 'srgb' | 'linear'` -- AI-user-semantic SSOT (charter P4\n * consistent abstraction); `format='*-srgb'` family <-> `colorSpace='srgb'`\n * enforced by `AssetRegistry.uploadTexture` consistency assertion.\n * - `mipmap: boolean` -- `true` enables runtime mipmap-generator blit chain\n * (research F-1 SSOT three-source convergence; plan-strategy section 2.6\n * D Open Q-5 (a) independent file). `false` ships the single mip level\n * authored in `data`.\n *\n * Both fields are required (no default value) so consumers always make the\n * decision explicit at register-time (charter P3 explicit failure: silent\n * default would mask sRGB encode mistakes).\n */\nexport interface TextureAsset {\n readonly kind: 'texture';\n readonly width: number;\n readonly height: number;\n readonly format: GPUTextureFormat;\n readonly data: Uint8Array | Uint8ClampedArray;\n readonly colorSpace: 'srgb' | 'linear';\n readonly mipmap: boolean;\n readonly mipLevelCount?: number;\n readonly sampleCount?: number;\n}\n\n/**\n * Equirectangular environment-map asset POD shape -- a single 2D HDR image in\n * latitude-longitude projection (feat-20260630-equirect-kind-internalized-ibl-\n * declarative-skyligh M1, replacing the prior `CubeTextureAsset`).\n *\n * `kind:'equirect'` is the build-time-imported `.hdr` artefact: a single\n * `rgba16float` 2D image whose pixels live in `data` (tight-packed, build .bin).\n * Unlike the retired cube-texture, an equirect HAS a single 2D representation,\n * so it folds to a build-time `.bin` like `TextureAsset` (the cube-to-cube IBL\n * projection is a GPU-side pass driven internally by the render-system record\n * arm; AI users declare `Skylight{equirect}` rather than calling an upload).\n *\n * Fields mirror the `TextureAsset` 2D-image surface (charter P4 consistent\n * abstraction): `width` / `height` / `format` / `data` / `colorSpace`. No\n * `mipmap` chain field -- the IBL prefilter mip chain is a GPU-side pass, not a\n * CPU-authored level set.\n */\nexport interface EquirectAsset {\n readonly kind: 'equirect';\n readonly width: number;\n readonly height: number;\n readonly format: GPUTextureFormat;\n readonly data: Uint8Array | Uint8ClampedArray;\n readonly colorSpace: 'srgb' | 'linear';\n}\n\n/**\n * Sampler asset POD shape aligned with `@webgpu/types ^0.1.69`\n * `GPUSamplerDescriptor` subset. AI users supply filter + address modes;\n * the RHI layer materialises the GPU-side sampler on upload.\n */\nexport interface SamplerAsset {\n readonly kind: 'sampler';\n readonly magFilter?: GPUFilterMode;\n readonly minFilter?: GPUFilterMode;\n readonly mipmapFilter?: GPUMipmapFilterMode;\n readonly addressModeU?: GPUAddressMode;\n readonly addressModeV?: GPUAddressMode;\n readonly addressModeW?: GPUAddressMode;\n readonly lodMinClamp?: number;\n readonly lodMaxClamp?: number;\n readonly compare?: GPUCompareFunction;\n readonly maxAnisotropy?: number;\n}\n\n// feat-20260613 fix-issue-4: MATERIAL_PARAM_TYPES_V1 (9-Set) deleted —\n// MATERIAL_PARAM_TYPES (14-tuple, declared below) is the single SSOT.\n// §Change stance forbids v1/v2 dual-paths; the 9 v1 literals are a strict\n// subset of the 14-tuple, so all consumers (buildMaterialAssetValidator,\n// scanner.ts) migrate to the 14-tuple in one cut.\n\n// === MaterialParamType v2 (feat-20260613-material-paramschema-driven-binding M1 / w2) ===\n//\n// Decision anchors:\n// - plan-strategy D-7 paramSchema type set v2 (9 v1 + 5 new = 14 literals)\n// - research finding F-1 the union of all binding types used by the 5 built-in\n// shaders (standard-pbr / pbr-skin / unlit / sprite / shadow-caster) is exactly\n// these 14 entries; CSM (texture_depth_2d) / point shadow (texture_cube_array) /\n// IBL (texture_cube) / sampler_comparison / storage_buffer (skin palette) all\n// already exist downstream\n// - charter P3 explicit failure: closed unions guard exhaustive switching with\n// no default arm; TS verifies completeness\n//\n// Shape:\n// - `MATERIAL_PARAM_TYPES` : 14-element readonly tuple, the SSOT whitelist\n// - `MaterialParamType` : string-literal union derived from the tuple\n// - `NumericParamType` : 7 numeric literals (run-merged into one UBO entry)\n// - `TextureBindingParamType`: 6 literals — texture* + sampler*\n// (per D-4 each texture* auto-pairs a filtering sampler in derive output;\n// sampler / sampler_comparison are user-declared schema entries)\n// - `StorageBindingParamType`: storage_buffer (independent binding)\n// - `ParamSchemaEntry` : discriminated union over the three families\n// (Numeric / TextureBinding / StorageBinding) — exhaustive switching\n// on `entry.type` is closed across the 14 literals.\n\n/** 7 numeric WGSL types — std140-packed into one merged UBO entry (D-3). */\nexport type NumericParamType = 'f32' | 'i32' | 'u32' | 'vec2' | 'vec3' | 'vec4' | 'color';\n\n/** 6 texture-binding-family WGSL types: 4 texture views + 2 sampler kinds. */\nexport type TextureBindingParamType =\n | 'texture2d'\n | 'texture_cube'\n | 'texture_depth_2d'\n | 'texture_cube_array'\n | 'sampler'\n | 'sampler_comparison';\n\n/** 1 storage-binding type (e.g. skin palette buffer). */\nexport type StorageBindingParamType = 'storage_buffer';\n\n/**\n * Closed union of WGSL material-parameter type literals (14 members).\n * Every paramSchema entry's `type` field MUST be a member of this union.\n */\nexport type MaterialParamType =\n | NumericParamType\n | TextureBindingParamType\n | StorageBindingParamType;\n\n/**\n * v2 material parameter type whitelist — 14 ordered literal tuple (D-7).\n * Order is significant only as a stable enumeration source for tests\n * and discoverability; consumers should treat membership as a Set.\n */\nexport const MATERIAL_PARAM_TYPES = [\n 'f32',\n 'i32',\n 'u32',\n 'vec2',\n 'vec3',\n 'vec4',\n 'color',\n 'texture2d',\n 'texture_cube',\n 'texture_depth_2d',\n 'texture_cube_array',\n 'sampler',\n 'sampler_comparison',\n 'storage_buffer',\n] as const satisfies readonly MaterialParamType[];\n\n// Numeric-family schema entry (run-merged into a single UBO entry by derive).\n// `default` is optional; when present, values may omit the key.\n// - scalar numeric (f32 / i32 / u32) defaults to a single number\n// - vector + color types default to a length-N number tuple\nexport interface NumericParamSchemaEntry {\n readonly name: string;\n readonly type: NumericParamType;\n /** Asset-side color transfer function; runtime UBO values are always linear. */\n readonly colorSpace?: 'srgb' | 'linear';\n readonly default?: number | readonly number[];\n}\n\n// Texture-binding-family schema entry (texture* / sampler*).\n// `default` is kept optional for backward compatibility with existing\n// schema fixtures that carry stray defaults; derive ignores it for the\n// non-numeric families (D-4 auto-pair rule resolves samplers; textures\n// resolve via Handle<TextureAsset>).\nexport interface TextureBindingParamSchemaEntry {\n readonly name: string;\n readonly type: TextureBindingParamType;\n readonly default?: unknown;\n}\n\n// Storage-binding-family schema entry (storage_buffer).\n// Always an independent binding (not merged into the UBO run); `default`\n// is optional and ignored by derive (backward compatibility shim).\nexport interface StorageBindingParamSchemaEntry {\n readonly name: string;\n readonly type: StorageBindingParamType;\n readonly default?: unknown;\n}\n\n// Re-export derive(schema) and its output shapes alongside the schema\n// type union so all downstream consumers (runtime / vite-plugin-shader /\n// shader-compiler) reach the SSOT through a single import surface (D-2).\nexport type {\n DerivedMaterialInterface,\n DerivedNumericMember,\n DeriveOutput,\n ImmutableParamSchemaProjection,\n MaterialCoordinateRecordLayout,\n MaterialParameterProjection,\n MaterialParameterResourceProjection,\n MaterialParameterTextureProjection,\n MaterialResourceBindingLayout,\n MaterialResourceKind,\n ParamSchemaDeriveObservationKind,\n ParamSchemaDeriveObserver,\n ParamSchemaProjectionOwnerStats,\n UboFieldLayout,\n UboLayout,\n} from './derive-paramschema.js';\nexport {\n derive,\n deriveObserved,\n findUndeclaredSampledTextures,\n inferMaterialParameterKind,\n ParamSchemaProjectionOwner,\n} from './derive-paramschema.js';\n\n/**\n * Single material parameter schema entry — discriminated union over the\n * three families (Numeric / TextureBinding / StorageBinding).\n *\n * `name` is the parameter identifier matching a WGSL binding name.\n * `type` is the discriminator — exhaustive `switch (entry.type)` covers the\n * 14 literals without a `default` arm; TS guards completeness (charter P3).\n */\nexport type ParamSchemaEntry =\n | NumericParamSchemaEntry\n | TextureBindingParamSchemaEntry\n | StorageBindingParamSchemaEntry;\n\n// === RenderQueue namespace constants (feat-20260526-material-asset-multipass-renderstate M1 / w3) ===\n//\n// Decision anchors:\n// - requirements AC-04 (5 standard queue values: Background=1000 / Geometry=2000 /\n// AlphaTest=2450 / Transparent=3000 / Overlay=4000)\n// - plan-strategy D-3 (queue values replace three-bucket dispatch)\n// - research F-5 (Utopia queue model; Transparent=3000 gives gap between\n// AlphaTest=2450 and Transparent=3000 for user custom queues)\n// - charter P1 (progressive disclosure: RenderQueue.Geometry autocomplete\n// exposes the value; AI users never need to memorize bare numbers)\n\n/**\n * Standard render queue constants (AC-04). AI users access via IDE autocomplete\n * (`RenderQueue.`) — no bare numbers to memorize (charter P1 progressive disclosure).\n *\n * Queue order (ascending):\n * Background(1000) -> Geometry(2000) -> AlphaTest(2450) -> Transparent(3000) -> Overlay(4000)\n *\n * The gap between AlphaTest(2450) and Transparent(3000) allows user-inserted\n * custom queues without colliding with either boundary (research F-5).\n */\nexport const RenderQueue = {\n /** Skybox / backdrop draw, processed first. */\n Background: 1000,\n /** Opaque geometry draw — default queue for solid surfaces. */\n Geometry: 2000,\n /** Alpha-tested geometry (clip/discard in fragment shader) — drawn after opaque,\n * before transparent to avoid overdraw. */\n AlphaTest: 2450,\n /** Transparent / alpha-blended geometry — drawn back-to-front after opaque pass. */\n Transparent: 3000,\n /** Overlay / UI / debug lines — drawn last. */\n Overlay: 4000,\n} as const;\n\n/** Type alias for the 5-member RenderQueue value union (1000 | 2000 | 2450 | 3000 | 4000). */\nexport type RenderQueue = (typeof RenderQueue)[keyof typeof RenderQueue];\n\n// === MaterialRenderState POD interface (feat-20260526-material-asset-multipass-renderstate M1 / w1) ===\n//\n// Decision anchors:\n// - requirements AC-03 (all fields optional; engine applies known defaults)\n// - plan-strategy D-2 (MaterialRenderState fields optional + engine static defaults)\n// - research F-3 (current hardcoded values become the defaults)\n// - research F-6 (Three.js taxonomy proves this subset is sufficient for LO 4.x)\n// - charter P1 (all fields optional reduces boilerplate; JSDoc on each field exposes\n// the default so AI users discover via IDE hover without reading prose)\n\n/**\n * Stencil face state sub-interface — mirrors `@webgpu/types.GPUStencilFaceState`\n * field-by-field (spec-aligned per RHI form rules). All fields optional so\n * consumers declare only the stencil behavior they need.\n */\nexport interface StencilFaceState {\n /** Default: `'never'`. */\n readonly compare?: GPUCompareFunction;\n /** Default: `'keep'`. */\n readonly failOp?: GPUStencilOperation;\n /** Default: `'keep'`. */\n readonly depthFailOp?: GPUStencilOperation;\n /** Default: `'keep'`. */\n readonly passOp?: GPUStencilOperation;\n}\n\n/**\n * Material render-state POD interface — all fields optional (AC-03).\n *\n * When a field is `undefined`, the pipeline-builder falls back to the\n * engine default value noted in each field's JSDoc. AI users only override\n * the fields that differ from the defaults (charter P1 progressive disclosure).\n *\n * The defaults are:\n * - `cullMode='back'` (back-face culling per WebGPU convention)\n * - `depthCompare='less'` (standard depth testing)\n * - `depthWriteEnabled=true` (write depth for opaque surfaces)\n * - `blend` undefined (no blending — opaque pass)\n * - `stencil` undefined (no stencil operations)\n * - `stencilReadMask` undefined (WebGPU default 0xFFFFFFFF)\n * - `stencilWriteMask` undefined (WebGPU default 0xFFFFFFFF)\n * - `frontFace` undefined (default 'ccw')\n */\nexport interface MaterialRenderState {\n /** Face culling mode. Default: `'back'` (cull back faces). */\n readonly cullMode?: 'none' | 'front' | 'back';\n /** Depth comparison function. Default: `'less'`. */\n readonly depthCompare?: GPUCompareFunction;\n /** Whether depth writes are enabled. Default: `true`. */\n readonly depthWriteEnabled?: boolean;\n /** Blend state descriptor (spec-aligned with `GPUBlendState`). Default: undefined (no blending). */\n readonly blend?: GPUBlendState;\n /** Enable alpha-to-coverage when the active camera uses MSAA. Default: `false`. */\n readonly alphaToCoverageEnabled?: boolean;\n /** Stencil face state. Default: undefined (no stencil operations). */\n readonly stencil?: StencilFaceState;\n /**\n * Stencil read mask (mirrors GPUDepthStencilState.stencilReadMask top-level).\n * Default: undefined (WebGPU default 0xFFFFFFFF).\n */\n readonly stencilReadMask?: number;\n /**\n * Stencil write mask (mirrors GPUDepthStencilState.stencilWriteMask top-level).\n * Default: undefined (WebGPU default 0xFFFFFFFF).\n */\n readonly stencilWriteMask?: number;\n /**\n * Front-face winding (mirrors GPUPrimitiveState.frontFace).\n * Default: `'ccw'`.\n */\n readonly frontFace?: 'ccw' | 'cw';\n}\n\n// === PassKind as open string + KNOWN_PASS_KINDS (feat-20260615-pipeline-spec-ssot D-10) ===\n//\n// Decision anchors:\n// - plan-strategy D-10 (PassKind opened from closed union to string; KNOWN_PASS_KINDS\n// is a discoverable documentation constant)\n// - requirements AC-09 (PassKind: open string; unknown passKind -> PipelineSpecError\n// code='unknown-pass-kind')\n// - charter P3 (fail-fast on unknown passKind via PipelineSpecError, not silent route)\n\n/**\n * Render-pass kind -- open string, no longer a closed union.\n *\n * `KNOWN_PASS_KINDS` (below) documents the engine-shipped pass kinds; user-defined\n * pass kinds are supported through {@link ShaderRegistry} registration. An unknown\n * pass kind triggers {@link PipelineSpecError} with code `'unknown-pass-kind'`\n * at pipeline-spec build time (charter P3 explicit failure).\n *\n * @see {@link KNOWN_PASS_KINDS} for the discoverable pass-kind catalogue\n * @see plan-strategy D-10 (PassKind opened from closed union)\n */\nexport type PassKind = string;\n\n/**\n * Engine-shipped pass kinds -- discoverable constant catalogue (D-10).\n *\n * Consumers iterate or lookup against this set to validate pass kinds before\n * submitting to `getOrBuildPipeline`. An unknown pass kind still triggers a\n * structured `PipelineSpecError` with code `'unknown-pass-kind'` carrying\n * `.detail.expected = KNOWN_PASS_KINDS` and `.detail.actual`.\n */\nexport const KNOWN_PASS_KINDS: readonly string[] = [\n 'forward',\n 'deferred',\n 'temporal',\n 'lighting',\n 'shadow-caster',\n 'post-process',\n 'skybox',\n] as const;\n\n// === PassSelector type (feat-20260526-material-asset-multipass-renderstate M1 / w4) ===\n//\n// Decision anchors:\n// - requirements AC-05 (tags + PassSelector matching: all selector keys must\n// exist in pass tags with value in the selector's value list)\n// - plan-strategy D-4 (Tags free Record + PassSelector Record<string, string[]>;\n// enum-based categories rejected — adding a pipeline stage should not require\n// editing the types package)\n// - charter P1 (type signature itself is the match-rule documentation;\n// `Record<string, string[]>` is self-describing)\n\n/**\n * Pass selector — maps tag keys to allowed value lists (AC-05).\n *\n * A pass matches the selector when **every** key in the selector exists in the\n * pass's `tags` and the pass's tag value is in the selector's value list for that key.\n * An empty selector matches every pass (no key constraints).\n *\n * The type signature itself is the API docs (charter P1): each entry maps a\n * tag key (string) to its allowed values (string array).\n *\n * @example Match passes tagged with `RenderType: 'Opaque'` or `RenderType: 'Transparent'`\n * ```ts\n * const selector: PassSelector = { RenderType: ['Opaque', 'Transparent'] };\n * ```\n */\nexport type PassSelector = Record<string, readonly string[]>;\n\n// === FontAsset POD shape (feat-20260531-world-space-msdf-text-rendering M2 / w5) ===\n//\n// Decision anchors:\n// - plan-strategy D-6 (FontAsset data shape: atlas Handle<TextureAsset> +\n// sampler Handle<SamplerAsset> + glyphs Record<codepoint, GlyphMetric> +\n// common block + optional notdef fallback; POD, math-free, fields 1:1\n// mirror toolchain wiki §4 BMFont char mapping)\n// - requirements AC-04 (FontAsset enters Asset closed union, 11->12)\n// - AGENTS.md §Component naming (single-semantic components drop the\n// Component suffix; FontAsset is a data asset, not an ECS component)\n//\n// GlyphMetric fields mirror the BMFont char block layout (toolchain wiki §4):\n// advance <- xadvance (horizontal distance to next glyph)\n// bearingX <- xoffset (horizontal offset from cursor)\n// bearingY <- yoffset (vertical offset from baseline)\n// size.{w,h} <- width/height (glyph quad size in layout space, before atlas\n// scale)\n// region.{x,y,w,h} <- atlas UV region in pixels (x/y = top-left corner\n// relative to atlas origin)\n\n/**\n * Per-glyph metric layout — 1:1 mirror of BMFont char block fields\n * (toolchain wiki §4). POD, math-free.\n */\nexport interface GlyphMetric {\n /** Horizontal distance to the next glyph (xadvance). */\n readonly advance: number;\n /** Horizontal offset from cursor (xoffset). */\n readonly bearingX: number;\n /** Vertical offset from baseline (yoffset). */\n readonly bearingY: number;\n /** Glyph quad size in layout space. */\n readonly size: { readonly w: number; readonly h: number };\n /** Atlas UV region in pixels (top-left origin). */\n readonly region: {\n readonly x: number;\n readonly y: number;\n readonly w: number;\n readonly h: number;\n };\n}\n\n/**\n * Font asset POD — atlas texture handle + sampler handle + per-codepoint\n * glyph metrics + common layout block.\n *\n * AI users obtain a FontAsset via `assets.load(guid, fontAssetKind)` and\n * hand the resulting `Handle<FontAsset>` to `GlyphText.fontHandle`. The atlas\n * texture and sampler are resolved through the handle chain by the glyph\n * layout system; the per-glyph metrics drive the quad-position-and-UV baking\n * (plan-strategy D-6).\n *\n * | Field | Purpose |\n * |:--|:--|\n * | `atlas` | Handle to the baked MSDF atlas `TextureAsset` |\n * | `sampler` | Handle to the `SamplerAsset` for atlas sampling |\n * | `glyphs` | `Record<codepoint, GlyphMetric>` — O(1) codepoint lookup |\n * | `common` | Common layout block (lineHeight / base / distanceRange / pxRange / atlas width/height) |\n * | `notdef` | Optional fallback glyph metric for missing codepoints (TOFU, AC-14) |\n */\nexport interface FontAsset {\n readonly kind: 'font';\n /**\n * Atlas texture GUID (D-19). Payload-internal sub-asset ref stored as an\n * AssetGuid, not a handle — the typed load recursion mints no handle; the\n * World-holding consumer (glyph-text-layout) resolves it once at read time.\n */\n readonly atlas: AssetGuid;\n /** Sampler GUID (D-19). Same GUID-identity contract as `atlas`. */\n readonly sampler: AssetGuid;\n readonly glyphs: Record<number, GlyphMetric>;\n readonly common: {\n readonly lineHeight: number;\n readonly base: number;\n readonly distanceRange: number;\n readonly pxRange: number;\n readonly atlasWidth: number;\n readonly atlasHeight: number;\n };\n readonly notdef?: GlyphMetric;\n}\n\n// === RenderPipelineAsset POD shape =============================================\n//\n// feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend M1 / w5.\n// Asset-layer descriptor that binds a registered render-pipeline logic id to an\n// installable Handle (the MaterialAsset { materialShaderId, params } pattern, scaled to\n// the pipeline layer). `installPipeline(handle)` resolves this POD off the AssetRegistry,\n// looks up `pipelineId` in the pipeline registry, and swaps the per-frame graph.\n//\n// M2 stage (w10): `config.passCount` is the FIRST real config key. The standard pipeline\n// runs with config undefined (default frame byte-identical, AC-01); a custom pipeline can\n// size its declared pass chain from `config.passCount` so its topology varies observably\n// via `renderer.perFramePassNames` (AC-03 / plan-strategy D-C).\n//\n// feat-20260608-cluster-lighting M2 / w8: `config.clusterGrid` is the HDRP cluster grid\n// dimensions config (default {x:16, y:9, z:24}). `pipelineId` is narrowed to a literal\n// union of `'forgeax::urp' | 'forgeax::hdrp' | (string & {})` so TS narrowing on\n// `pipelineId === 'forgeax::hdrp'` narrows `config.clusterGrid`.\n//\n// feat-20260612-hdrp-ssao M4 / w19: `config.ssao` is the SSAO configuration\n// (enabled+radius+bias+intensity). Same shared-config pattern as clusterGrid;\n// HDRP consumes it, URP ignores it at runtime.\n/**\n * Asset-layer descriptor for an installable render pipeline.\n *\n * `pipelineId` references a logic registered via `renderer.registerPipeline(id, impl)`\n * (engine builtins use the `forgeax::` prefix, e.g. `'forgeax::urp'`; user pipelines use\n * `<package>::<id>`). `config` is the per-install tuning the pipeline logic reads at\n * `buildGraph` time; `passCount` is the first real key (a custom pipeline declares that\n * many passes). The URP ignores `config` (its topology is fixed).\n *\n * `config.clusterGrid` is the HDRP cluster grid dimensions (x/y/z each an integer in\n * [1, 64]; default {16, 9, 24}). It is ignored by URP.\n *\n * `config.ssao` enables SSAO (Screen-Space Ambient Occlusion) for the HDRP\n * deferred path. Ignored by URP.\n */\nexport interface RenderPipelineAsset {\n readonly kind: 'render-pipeline';\n readonly pipelineId: 'forgeax::urp' | 'forgeax::hdrp' | (string & {});\n readonly config?: {\n readonly passCount?: number;\n readonly clusterGrid?: { readonly x: number; readonly y: number; readonly z: number };\n readonly ssao?: {\n readonly enabled: boolean;\n readonly radius?: number | undefined;\n readonly bias?: number | undefined;\n readonly intensity?: number | undefined;\n };\n /**\n * feat-20260621 M4': ordered registered post-process shader ids the built-in\n * pipelines composite over the FINAL swap-chain image, after the fxaa pass\n * and before the debug overlay. Each id must be registered via\n * `renderer.postProcess.register(id, entry)` first. The effects run in array\n * order; each samples the current swap-chain (copy) and writes it back, so a\n * chain composes left-to-right. This is the AUGMENT path: the built-in 9-pass\n * chain (shadow cascades, tonemap, bloom, fxaa) renders unchanged and the\n * effects layer on top — unlike installing a wholly custom pipeline, which\n * REPLACES the built-in graph (and would drop its shadow passes). `undefined`\n * or `[]` adds zero passes (default frame byte-identical).\n *\n * WebGPU backend only: each effect reads the mid-frame swap-chain (copy +\n * non-srgb storage-view write), which the WebGL2 fallback swap-chain does not\n * support (no COPY_SRC, no non-srgb reinterpret view) — same constraint the\n * built-in FXAA pass already carries. On a non-WebGPU device leave this empty.\n */\n readonly postEffects?: readonly string[];\n };\n}\n\n/**\n * Asset discriminated union - 13 variants keyed on `.kind`.\n *\n * Variant history:\n * - feat-20260513-instanced-mesh M1 introduced a 5th `'instanced-buffer-asset'`\n * variant carrying packed mat4 transforms + a `version` dirty flag.\n * - feat-20260514-ecs-children-instances-managed-buffer-array M3 (w15)\n * retired that variant: per-entity instanced transforms are now stored\n * directly inside the ECS via the `Instances { transforms: 'array<f32>' }`\n * component (managed by the BufferPool slot column + sidecar count\n * column). Asset closed-union shrinks 5 -> 4 (evolution major rename\n * per AGENTS.md `Change stance`); existing exhaustive `switch\n * (asset.kind)` consumers drop the now-unreachable arm in the same PR.\n * - feat-20260514-scene-as-world-blueprint w3 added `'scene'` variant\n * (4 -> 5, minor add per AGENTS.md `Evolution contract`); declarative\n * SceneEntity list, no overrides at the asset layer.\n * - feat-20260531-world-space-msdf-text-rendering w5 added `'font'` variant\n * (11 -> 12, minor add per AGENTS.md `Evolution contract`);\n * FontAsset with atlas handle + sampler handle + glyph metrics.\n * - feat-20260601-customizable-render-pipeline-seam-and-dogfood-rend w5 added\n * `'render-pipeline'` variant (12 -> 13, minor add per AGENTS.md\n * `Evolution contract`); RenderPipelineAsset with pipelineId + config.\n * - feat-20260623-world-space-video-asset M1 added `'video'` variant\n * (14 -> 15, minor add per AGENTS.md `Evolution contract`);\n * VideoAsset with `{ url }` descriptor, no width/height/duration.\n *\n * Exhaustive `switch (asset.kind)` type-guards against future additions\n * without default fallback (charter proposition 4 + proposition 3).\n *\n * | kind | variant |\n * |:--|:--|\n * | `'mesh'` | `MeshAsset` |\n * | `'texture'` | `TextureAsset` |\n * | `'equirect'` | `EquirectAsset` (single 2D HDR lat-long env map for IBL) |\n * | `'sampler'` | `SamplerAsset` |\n * | `'material'` | `MaterialAsset` (further narrows on `.passes`) |\n * | `'scene'` | `SceneAsset` (declarative SceneEntity list, no overrides) |\n * | `'font'` | `FontAsset` (MSDF atlas handle + glyph metrics) |\n * | `'render-pipeline'` | `RenderPipelineAsset` (installable pipeline logic id + config) |\n * | `'video'` | `VideoAsset` (runtime-only `{ url }` descriptor, no width/height/duration) |\n * | `'animation-graph'` | `AnimationGraph` (Clip/Blend/Add DAG + per-node static weights) |\n */\nexport type Asset =\n | MeshAsset\n | TextureAsset\n | EquirectAsset\n | SamplerAsset\n | MaterialAsset\n | SceneAsset\n | SkeletonAsset\n | SkinAsset\n | AnimationClip\n // === 1 new variant (feat-20260713-animation-state-machine-plugin M2 / w13) ===\n // AnimationGraph POD (Clip/Blend/Add DAG); GUID-addressable, serializable\n // (AC-14 foundation). Closed node union (clip/blend/add), no reserved FSM/Mask\n // fields (OOS-1..4).\n | AnimationGraph\n | AudioClipAsset\n | FontAsset\n | RenderPipelineAsset\n // === 1 new variant (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n // Direct atlases[] form (plan-strategy D-7 one-cut); no intermediate single-`atlas` shape.\n | TilesetAsset\n // === 1 new variant (feat-20260623-world-space-video-asset M1) ===\n // runtime-only { url } descriptor; no width/height/duration in payload.\n | VideoAsset\n | ParticleEffectAsset;\n\n// === Tileset asset POD shape (feat-20260608 M0 baseline rebuild) =================\n//\n// Decision anchors:\n// - requirements §AC-01/03/04/05 (TilesetAsset 9 fields; atlases plural composite;\n// M0 TilesetTileEntry single-field shape with M1 adding 5 optional + collider).\n// - plan-strategy §D-5 (M0 baseline rebuild after main reverted feat-20260604).\n// - plan-strategy §D-7 (`atlases: readonly Handle<TextureAsset,managed>[]` one-cut\n// rename; no `atlas` single-form alias or dual-path).\n// - plan-strategy §D-6 (AssetErrorCode count restoration -- M0 reintroduces\n// `tileset-region-index-out-of-range`; M1 adds `tileset-tile-entry-malformed`).\n// - charter F1 (AI users discover the schema via IDE autocomplete on the closed\n// `Asset` union + `Handle<TilesetAsset>` returns from `AssetRegistry.register`).\n// - charter P4 (atlases plural composite mirrors `MaterialAsset.passes[]` shape).\n\n/**\n * Closed `TilesetTileCollider` union -- per-tile collider schema (M1\n * extension; feat-20260608-tilemap-object-layer-rendering M1 / m1-t2).\n *\n * Three discriminant variants (closed enum, charter P3):\n *\n * - `{ type: 'none' }` -- no collider for this tile.\n * - `{ type: 'rect', rect: readonly [x, y, w, h] }` -- axis-aligned\n * rectangle in normalized cell coordinates `[0, 1]^2`. `w > 0`, `h > 0`,\n * `x + w <= 1`, `y + h <= 1` are enforced by `validateTilesetPayload`\n * (R-6 first-error path).\n * - `{ type: 'polygon', points: readonly [x, y][] }` -- convex/concave\n * polygon in normalized cell coordinates. `points.length >= 3` and\n * each point lies in `[0, 1]^2`.\n *\n * The engine validates this schema at register-time but does NOT consume\n * it (plan-strategy §D-4 -- schema landed, consumer deferred to a future\n * `feat-tilemap-physics-bridge` closed loop). AI users with a physics\n * sidecar consume the schema directly via `tileset.tiles[i].collider`\n * after `assets.register<TilesetAsset>(...)` resolves the handle.\n *\n * Exhaustive switch:\n * ```ts\n * switch (collider.type) {\n * case 'none': return null;\n * case 'rect': return collider.rect;\n * case 'polygon': return collider.points;\n * // No default branch -- TS guards completeness (charter P3).\n * }\n * ```\n */\nexport type TilesetTileCollider =\n | { readonly type: 'none' }\n | { readonly type: 'rect'; readonly rect: readonly [number, number, number, number] }\n | { readonly type: 'polygon'; readonly points: readonly (readonly [number, number])[] };\n\n/**\n * Rectangular sub-region within a tileset atlas (M1 schema extension on\n * top of M0 baseline rebuild).\n *\n * Four required fields define the atlas-space rectangle in pixels:\n * `x` / `y` top-left corner; `width` / `height` extent. `width + x` and\n * `height + y` MUST stay within the parent atlas extent or\n * `validateTilesetPayload` returns\n * `AssetError { code: 'tileset-region-index-out-of-range' }`\n * (charter P3 explicit failure at register-time).\n *\n * Optional `atlasIndex?: number` (M1; default 0) routes the region into\n * `TilesetAsset.atlases[atlasIndex]` for multi-atlas tilesets. Out-of-range\n * `atlasIndex` (`>= atlases.length` or negative) surfaces\n * `AssetError { code: 'tileset-tile-entry-malformed', detail: { field: 'atlasIndex', scope: 'tileset-asset' } }`\n * at register-time (plan-strategy §D-7 three-hop routing; R-6 first-error\n * order places atlasIndex check between region rect bounds and per-tile\n * entry field checks).\n */\nexport interface TilesetRegion {\n readonly x: number;\n readonly y: number;\n readonly width: number;\n readonly height: number;\n readonly atlasIndex?: number;\n}\n\n/**\n * Per-tile entry in `TilesetAsset.tiles[]` (M1 schema extension on top of\n * M0 baseline rebuild).\n *\n * Required `regionIndex` points into the parent `TilesetAsset.regions[]`\n * array. M1 adds five optional fields for the variable-size + custom-pivot\n * object-layer story (plan-strategy §M1):\n *\n * - `widthCells?: number` -- multi-cell width in `Tilemap.cols/rows`\n * coordinate units (default 1; range `(0, 64]`). Anchored at the\n * cell that hosts the non-zero tileId entry; cells inside the\n * `widthCells x heightCells` footprint must stay 0 in `TileLayer.tiles[]`\n * (anchor convention, not enforced at register time).\n * - `heightCells?: number` -- multi-cell height (default 1; range `(0, 64]`).\n * - `pivotX?: number` -- normalized horizontal pivot in `[0, 1]` (default 0.5).\n * The pivot is the world-space anchor: `pivot_world_X = (cellX + pivotX) * tileSizeX`.\n * Quad center is offset by `(0.5 - pivotX) * widthCells * tileSizeX`\n * (plan-strategy §D-2 first-line geometry; M2 implementation).\n * - `pivotY?: number` -- normalized vertical pivot in `[0, 1]` (default 0.5).\n * For asi_world `.tsj` extension: `pivotY = 1.0` means quad bottom\n * anchors the cell, `pivotY = 0.0` means quad top (per-asset convention,\n * not Tiled native). The engine uses `effectivePivotY` for Y-sort.\n * - `collider?: TilesetTileCollider` -- 3-variant closed union schema\n * (charter P3). Engine validates the schema at register-time but does\n * NOT consume it (plan-strategy §D-4).\n *\n * All five fields are optional so unit-cell call sites `{ regionIndex: N }`\n * remain backward compatible (charter F1).\n *\n * Out-of-range `regionIndex` (>= regions.length, or negative) surfaces\n * `AssetError { code: 'tileset-region-index-out-of-range' }`. Out-of-range\n * `widthCells / heightCells / pivotX / pivotY / collider` surface\n * `AssetError { code: 'tileset-tile-entry-malformed', detail: { field, scope: 'tile-entry', tileEntryIndex } }`\n * (plan-strategy §D-6 closed 7-variant `.detail.field` enum).\n */\nexport interface TilesetTileEntry {\n readonly regionIndex: number;\n readonly widthCells?: number;\n readonly heightCells?: number;\n readonly pivotX?: number;\n readonly pivotY?: number;\n readonly collider?: TilesetTileCollider;\n}\n\n/**\n * Tileset asset (M0 baseline rebuild on origin/main).\n *\n * Nine fields:\n * - `kind` -- discriminator literal `'tileset'`.\n * - `atlases` -- one or more durable GUIDs for atlas textures.\n * `atlases.length >= 1` enforced at register time.\n * M0 reads `atlases[0]` exclusively (single-atlas form);\n * M1 adds `regions[].atlasIndex` for multi-atlas routing.\n * - `tileWidth`/`tileHeight` -- per-cell pixel size (atlas grid stride).\n * - `columns`/`rows` -- atlas grid layout (informational metadata; used\n * as fallback when `atlasSizes` is absent to infer\n * atlas pixel extent as `columns * tileWidth`).\n * - `atlasSizes` -- optional per-atlas pixel dimensions. When present,\n * `atlasSizes[i]` gives the exact pixel size of\n * `atlases[i]` and overrides `columns`/`rows` for UV\n * normalisation in the chunk-extract system. Required when\n * the tileset contains multiple atlases with different\n * pixel sizes (e.g. a terrain + object composite tileset).\n * Each entry carries `{ pixelWidth, pixelHeight }`.\n * - `regions` -- array of atlas sub-rectangles (TilesetRegion).\n * - `tiles` -- per-tile entries (TilesetTileEntry), 1-indexed via tile id\n * sentinel where 0 means \"empty\" in `TileLayer.tiles`.\n *\n * Plural composite `atlases` (not single `atlas`) is the one-cut breaking\n * rename versus the old feat-20260604 form (plan-strategy §D-7 + AGENTS.md\n * §Change stance \"optimal > compatible\"); no deprecation alias survives.\n *\n * @example Register a tileset and spawn a Tilemap + TileLayer pair:\n * ```ts\n * const atlasGuid = 'world/object_atlas';\n * const tileset = registry.register<TilesetAsset>({\n * kind: 'tileset',\n * atlases: [atlasGuid],\n * tileWidth: 16,\n * tileHeight: 16,\n * columns: 8,\n * rows: 8,\n * regions: [{ x: 0, y: 0, width: 16, height: 16 }],\n * tiles: [{ regionIndex: 0 }],\n * }).unwrap();\n * ```\n */\nexport interface TilesetAtlasSize {\n readonly pixelWidth: number;\n readonly pixelHeight: number;\n}\n\nexport interface TilesetAsset {\n readonly kind: 'tileset';\n readonly atlases: readonly string[];\n readonly tileWidth: number;\n readonly tileHeight: number;\n readonly columns: number;\n readonly rows: number;\n /** Per-atlas pixel dimensions. `atlasSizes[i]` overrides `columns`/`rows`\n * for UV normalisation of regions whose `atlasIndex === i`. Required when\n * atlases have different pixel sizes. */\n readonly atlasSizes?: readonly TilesetAtlasSize[];\n readonly regions: readonly TilesetRegion[];\n readonly tiles: readonly TilesetTileEntry[];\n}\n\n// === AssetRef + AssetEnvelope (feat-20260622-asset-ref-graph-protocol-unification-refs-as-ssot M1 / w1) ===\n//\n// Decision anchors:\n// - plan-strategy D-1: single envelope type AssetEnvelope = { guid, kind, name?,\n// payload, refs } — ImportedAsset is upgraded to this shape; assetCatalog\n// value type changes from Map<string, Asset> to Map<string, AssetEnvelope>.\n// - plan-strategy D-2: scene refs are importer flat superset (mesh U material U\n// texture U skeleton U skin); texture edges have sourceField=undefined (no\n// per-entity origin).\n// - plan-strategy D-3: sourceField is structured triple { componentName,\n// fieldName, arrayIndex? } — consumers read via property access, not string\n// parse (charter P3).\n// - plan-strategy D-10: edge metadata (AssetRef) does NOT sink into Loader.load\n// refs param — loader still receives GUID string projection.\n// - plan-strategy OOS-1: Asset closed union unchanged; AssetEnvelope wraps it.\n// - plan-strategy OOS-4: AssetErrorCode member set unchanged (21 members).\n// - charter F1: single-entry indexability — refs field name cross-layer\n// consistent (ImportedAsset.refs / AssetEnvelope.refs / pack-index refs).\n\n/**\n * Structured edge metadata carried in an asset envelope's `refs[]`. Each entry\n * records a GUID-level reference plus optional provenance: which scene entity\n * field originated the reference (``sourceField``) and the entity's local id\n * (``sceneEntityId``). Texture edges and other transitive references have no\n * per-entity origin — ``sourceField`` is ``undefined`` for those (D-2).\n *\n * AI users consume ``sourceField`` via property access (``ref.sourceField?.componentName``\n * / ``ref.sourceField?.fieldName`` / ``ref.sourceField?.arrayIndex``), never by\n * parsing a concatenated string (charter P3).\n */\nexport interface AssetRef {\n readonly guid: string;\n readonly sourceField?: {\n readonly componentName?: string;\n readonly fieldName: string;\n readonly arrayIndex?: number;\n };\n readonly sceneEntityId?: number;\n}\n\n/**\n * Self-contained asset envelope — the single shape through which assets flow\n * from import to catalog to recursive load (plan-strategy D-1).\n *\n * ``payload`` carries the closed ``Asset`` union member (mesh / texture / scene /\n * material / etc.). ``refs`` is the authoritative reference graph — every GUID\n * this asset transitively depends on, with optional edge metadata\n * (``sourceField`` / ``sceneEntityId``). ``name`` is the per-asset display name\n * (may be undefined; ``resolveName`` derives the final name via a three-argument\n * XOR that also considers the package path).\n */\nexport interface AssetEnvelope<P = Asset> {\n readonly guid: string;\n readonly kind: string;\n // Per-GUID stored display name -- the `storedName` argument resolveName feeds\n // to deriveAssetName (the single home for the explicit name, replacing the\n // retired storedNameOf side table). `undefined` = no explicit name (resolveName\n // then applies the multi-asset basename fallback / no-package '' branch).\n readonly name?: string;\n readonly payload: P;\n readonly refs: readonly AssetRef[];\n}\n\n// === Package interface (feat-20260618-asset-and-pack-name-fields M1 / w2) ======\n//\n// Decision anchors:\n// - plan-strategy D-7 (Package interface in @forgeax/engine-types, same layer\n// as Asset union, for multi-package consumer discoverability per charter F1)\n// - architecture-principles #2 (Derive, Don't Duplicate): assetCount is\n// derived from assetGuids.size, never stored independently\n// - plan-strategy D-5 (builtin assets -> null Package, not a synthetic path)\n// - Package does not carry a `name` field — resolved names flow through\n// resolveName (D-6), not stored on Package\n//\n// AI users discover Package via IDE autocomplete on @forgeax/engine-types;\n// the runtime AssetRegistry.packageOf Map carries Package | null per guid.\n\n/**\n * Runtime view of one import-source package -- the grouping unit for\n * the two-segment asset identity (`<packagePath>.<name>`).\n *\n * `path` is the import file path (e.g. `'assets/hero.glb'`). Multiple\n * assets imported from the same source file share one `Package`.\n *\n * `assetGuids` lists every GUID that belongs to this package. The\n * runtime keeps it in sync with `registerPackage` insertions.\n *\n * `assetCount` is a derived view (`assetGuids.size`); it is **not**\n * stored as a standalone field (Derive axiom #2).\n */\nexport interface Package {\n readonly path: string;\n readonly assetGuids: ReadonlySet<string>;\n readonly assetCount: number;\n}\n\n// === Scene asset POD shape (feat-20260514-scene-as-world-blueprint w2) ==========\n//\n// Decision anchors:\n// - requirements §AC-01 (AssetUnion 6 elements; SceneAsset top level only\n// `kind` + `entities`, no overrides field at the asset layer)\n// - requirements §AC-02 (LocalEntityId branded number; cross-brand assignment\n// to / from Entity is a TS compile-time error)\n// - charter proposition 1 (single-entry IDE autocomplete from\n// `@forgeax/engine-types`) + proposition 4 (explicit failure: brand\n// phantom rejects untagged number) + proposition 5 (consistent\n// abstraction, structurally parallel to Handle<T> brand)\n//\n// The unique-symbol brand stays private to this module so the brand\n// identity is anchored exactly here; consumers refer to LocalEntityId\n// as opaque number subtypes.\n\ndeclare const LocalEntityIdBrand: unique symbol;\n\n/**\n * Scene-local entity index brand (u32).\n *\n * Authored as `0..entities.length-1` inside a SceneAsset; runtime storage stays\n * a plain JS number (the phantom `[LocalEntityIdBrand]` is erased at runtime\n * but rejects cross-brand assignment with `Entity` at\n * the TS layer).\n *\n * AI users obtain LocalEntityId values from `SceneEntity.localId` accessors and\n * hand them back to `SceneEntity` manipulation methods; plain\n * `number` is not assignable to `LocalEntityId` by design — see\n * `packages/types/src/__tests__/scene-brand.test-d.ts` for the negative\n * assertions.\n */\nexport type LocalEntityId = number & { readonly [LocalEntityIdBrand]: void };\n\n/**\n * Open map shape from component name to the per-component value record\n * authored on a `SceneEntity` (feat-20260514 w2).\n *\n * The map is keyed by component-token name (`'Transform' | 'MeshFilter' |\n * 'ChildOf' | ...`) and each per-component record is a free-form\n * `Record<string, unknown>` POD shape; the precise field types live in the\n * ecs `defineComponent(...)` schema (one layer up). This package stays\n * math-free + ecs-free; the layered alignment with the ecs schema vocab is\n * documented in plan-strategy §3.1 types_pkg sub-graph and tested by w3 /\n * w22 at the ecs / runtime layer.\n *\n * Open shape is intentional: components evolve via add-only minor in their\n * own packages; locking this map to a closed union here would force an edit\n * in @forgeax/engine-types every time a new component appears (charter\n * proposition 5 consistent abstraction — registration discipline owned by\n * each component's defineComponent site).\n */\nexport type ComponentValuesMap = {\n readonly [componentName: string]: Readonly<Record<string, unknown>>;\n};\n\n/**\n * Single SceneEntity POD shape (feat-20260514 w2).\n *\n * Carries one `localId` (LocalEntityId brand) plus a partial map of explicit\n * component field values. The partial keying lets layer 1 (explicit) leave\n * any component absent so layer 2 (component-level defaults) and layer 3\n * (TS type defaults) can fill in the residual fields at instantiate time\n * (plan-strategy §default-values 4-layer fallback table; AC-07 / AC-11 /\n * AC-12 sites).\n */\nexport interface SceneEntity {\n readonly localId: LocalEntityId;\n readonly components: Partial<ComponentValuesMap>;\n}\n\n/**\n * One per-member mount-time modification (add-or-patch) applied to a\n * mounted scene member (feat-20260608-scene-nesting-ecs-fication M1 / w7;\n * feat-20260713-mount-override-component-add-and-shared-ref-round M1 / w2;\n * AC-01, AC-19).\n *\n * `localId` selects a member entity inside the mounted SceneAsset,\n * counted against the mount's `memberFirst` window. `comp` names the\n * target component. `field` is a component-granular discriminant:\n * - present -> PATCH one field: `value` is that single field's value,\n * written over the member's existing component at instantiate time;\n * - absent -> ADD/UPSERT the whole component: `value` is the per-field\n * value map for `comp`, merged onto (or creating) the member's\n * component.\n * `value` stays `unknown` because the per-component schema vocab lives one\n * layer up — runtime fail-fast via 'scene-override-type-mismatch' catches\n * type drift (plan-strategy D-9 / D-1). The discriminant is carried by the\n * shape itself (field present / absent), never a separate `op` tag, so no\n * consumer branches on a `switch (op)` (requirements: consumers do not\n * encode variant knowledge).\n *\n * Boundary notes: add is upsert (an existing component is merged, not\n * rejected); a NULL-sentinel entity value in the payload follows the same\n * `entity` remap rules as SceneEntity.components; overrides apply in\n * `mounts[].overrides[]` array order after the member's own authored\n * components.\n *\n * Charter mapping: proposition 1 (single-entry import surface, three-tier\n * progressive disclosure) + proposition 3 (machine-readable union,\n * MountOverride is a closed POD shape) + proposition 4 (explicit failure:\n * runtime apply path returns Result with structured error code).\n */\nexport interface MountOverride {\n readonly localId: LocalEntityId;\n readonly comp: string;\n readonly field?: string;\n readonly value: unknown;\n}\n\n/**\n * One mount instance authored on a parent SceneAsset\n * (feat-20260608-scene-nesting-ecs-fication M1 / w7; AC-01).\n *\n * A mount embeds another SceneAsset (referenced by `source`, a dual-carrier:\n * `number` = at-rest refs[] index; `string` = post-parse / post-collect GUID\n * string) into the parent's namespace. The mount reserves a contiguous\n * LocalEntityId window\n * `[memberFirst, memberFirst + memberCount)` for the embedded scene's\n * member entities so the parent SceneAsset's namespace invariant\n * `totalSlots = entities.length + mounts.length + sum(memberCount)`\n * holds (plan-strategy §6.3 + requirements §S-1).\n *\n * Optional fields:\n * - `parent`: LocalEntityId in the *parent* scene to which the mount\n * attaches (defaults to the parent scene's outermost root,\n * requirements-decisions §D-4);\n * - `components`: per-component value overlay applied to the mount\n * entity itself (mirrors SceneEntity.components shape; carries the\n * same Partial<ComponentValuesMap> typing);\n * - `overrides`: an array of MountOverride records that further\n * specialise individual member entities at mount-time (AC-19).\n *\n * Charter mapping: proposition 1 (single-entry import surface;\n * SceneInstanceMount sits next to SceneEntity / SceneAsset) +\n * proposition 5 (consistent abstraction: the field set mirrors\n * SceneEntity for AI-user discoverability).\n */\nexport interface SceneInstanceMount {\n readonly localId: LocalEntityId;\n readonly source: number | string;\n readonly memberFirst: LocalEntityId;\n readonly memberCount: number;\n readonly parent?: LocalEntityId;\n readonly components?: Partial<ComponentValuesMap>;\n readonly overrides?: readonly MountOverride[];\n /** Engine-owned publication identity for generated Scene mounts. */\n readonly publicationFence?: import('./asset-producer').ScenePublicationFence;\n}\n\n/**\n * Scene asset POD shape (feat-20260514 w2; sixth member of the closed\n * `Asset` union).\n *\n * Three top-level fields — `kind: 'scene'` discriminator,\n * `entities: readonly SceneEntity[]`, and the optional `mounts:\n * readonly SceneInstanceMount[]` (feat-20260608-scene-nesting-\n * ecs-fication M1 / w7; AC-01). Per-instance overrides at the\n * asset layer are still absent (charter proposition 5: ECS write\n * paths and prefab override paths are explicitly disjoint,\n * plan-strategy §3.2 sequence B); the `mounts[]` window is an\n * authoring-time graph edge, not a write path.\n *\n * Back-compat: `mounts` is optional so legacy SceneAsset values\n * remain assignable; missing `mounts` is semantically equivalent to\n * `mounts: []` (plan-strategy §6.3, ajv default `[]`).\n */\nexport interface SceneAsset {\n readonly kind: 'scene';\n readonly entities: readonly SceneEntity[];\n readonly mounts?: readonly SceneInstanceMount[];\n /**\n * GUIDs of `SkinAsset`s the scene's skinned entities reference (one per\n * SkeletonAsset bound by a `Skin: { skeleton }` component). SkinAssets are\n * not reachable through any `handle<*>` field on a SceneEntity component\n * (`Skin.skeleton` carries the SkeletonAsset GUID; the SkinAsset itself is\n * a sibling identified by matching `skeletonGuid`), so the scene's pack\n * load chain has to surface them explicitly. Without this list the\n * browser-async-pack-fetch path would never load SkinAssets, leaving\n * `postSpawnResolveJoints` unable to populate `Skin.joints[]` and the\n * extract pass fail-fasting on `Skin.joints.length=0` every frame\n * (feat-20260612-skin-palette-per-frame-upload M2 fixup).\n *\n * On disk: refs[] indices, mirror of `mounts[].source`.\n * Post-parseScenePayload: GUID strings (resolved via refs[]).\n * Enumerated in the scene envelope's `refs[]` (the recursion source) so\n * `AssetRegistry.load(sceneGuid, sceneAssetKind)` recursively pulls each SkinAsset before\n * `instantiate`.\n */\n readonly skinGuids?: readonly string[];\n}\n\n// === Skeleton / Skin / AnimationClip asset POD shapes (feat-20260523-skin-skeleton-animation M0) ===\n//\n// Decision anchors:\n// - requirements AC-01 (SkeletonAsset shape: kind, guid, inverseBindMatrices Float32Array, jointCount)\n// - requirements AC-04 (Skin sub-asset shape: kind, guid, skeletonGuid, jointPaths)\n// - requirements AC-07 (AnimationClip shape: kind, guid, duration, channels)\n// - plan-strategy D-1 (3-asset separation: IBM/Skin bindings/AnimationClip curves physically independent)\n// - charter P3 (explicit failure: all shape carry typed fields, no loose Record<string,unknown> payloads)\n//\n// AnimationChannel / AnimationSampler sub-types are inline here since they are\n// exclusively consumed by AnimationClip; no other asset or component references them.\n\n/** Stable target identity linking a sampler to a scene entity. */\nexport interface AnimationChannel {\n /** Stable animation target identity. */\n readonly targetId: AnimationTargetIdValue;\n /** Target transform property: 'translation' | 'rotation' | 'scale' | 'weights'. */\n readonly property: 'translation' | 'rotation' | 'scale' | 'weights';\n /** Sampler driving this channel. */\n readonly sampler: AnimationSampler;\n}\n\n/**\n * Animation sampler — keyframe curve for a single animation-target-property pair.\n *\n * `input` and `output` are Float32Arrays of equal length\n * (`output.length = input.length * elementCount`) where elementCount is:\n * - 3 for 'translation' / 'scale' (vec3)\n * - 4 for 'rotation' (quat)\n * - targetCount for 'weights' (morph weights)\n *\n * `interpolation` is restricted to LINEAR and STEP per D-1 scope;\n * CUBICSPLINE is deferred to OOS-skin-cubicspline (fail-fast at importer).\n */\nexport interface AnimationSampler {\n readonly input: Float32Array;\n readonly output: Float32Array;\n readonly interpolation: 'LINEAR' | 'STEP';\n}\n\n/**\n * Animation clip asset POD shape.\n *\n * `duration` is max(sampler.input[last]) across all channels — the\n * longest channel defines the clip length. Each channel targets one\n * animation-target property, resolved during playback by `targetId`.\n */\nexport interface AnimationClip {\n readonly kind: 'animation-clip';\n readonly duration: number;\n readonly channels: readonly AnimationChannel[];\n}\n\n// === AnimationGraph asset POD + node union (feat-20260713 M2 / w13) ==============\n//\n// Decision anchors:\n// - requirements AC-02 (declarative Clip/Blend/Add + nesting graph carried as\n// a shared<AnimationGraph> asset handle, multi-entity shared).\n// - requirements AC-14 (AnimationGraph joins the closed `Asset` union, owns a\n// GUID, and serializes into pack/scene round-trip — foundation landed here,\n// the serialize/deserialize mechanism itself is M4).\n// - requirements OOS-1/OOS-2/OOS-3/OOS-4 (node union is CLOSED at three\n// variants — clip / blend / add. No FSM state/transition, no bone Mask, no\n// built-in transition layer, no BlendSpace fields are reserved; deferred\n// features add nodes in a future closed loop, not speculative fields now —\n// charter F4 \"no unvalidated abstraction\").\n// - requirements OOS-7 (POD carries only the topology of an engine-authored\n// defineAnimationGraph graph; no DCC import metadata).\n// - plan-strategy D-4 (POD + node union land in types/index.ts, the single-file\n// SSOT for every Asset POD, alongside AnimationClip).\n//\n// The POD mirrors AnimationClip: no inline `guid` field — the GUID is assigned by\n// the AssetRegistry / shared-handle system when the graph is registered (like\n// AnimationClip / MaterialAsset / VideoAsset). Nodes are stored flat in `nodes[]`\n// and referenced by index; `root` is the index of the output node. Clip leaves\n// carry a durable GUID string; the runtime consumer resolves that GUID to a\n// World-local transient shared handle only at evaluation time.\n\n/**\n * Clip leaf node — samples a single `shared<AnimationClip>` at the node's\n * runtime seek-time (M3 evaluation). `weight` is the node's STATIC weight; the\n * effective weight is `runtime weight x static weight` (requirements AC-07\n * orthogonal product).\n */\nexport interface AnimationGraphClipNode {\n readonly type: 'clip';\n readonly clip: string;\n readonly weight: number;\n}\n\n/**\n * Blend node — normalizing lerp over its children (requirements AC-04). Child\n * effective weights are normalized so they sum to 1 at evaluation. `children`\n * are indices into the parent {@link AnimationGraph.nodes} array.\n */\nexport interface AnimationGraphBlendNode {\n readonly type: 'blend';\n readonly children: readonly number[];\n readonly weight: number;\n}\n\n/**\n * Add node — non-normalizing additive stack (requirements AC-05). The `base`\n * child contributes its effective weight unchanged; each `additive` layer is\n * added on top WITHOUT normalization (total may exceed 1). `base` and\n * `additive` are indices into {@link AnimationGraph.nodes}.\n */\nexport interface AnimationGraphAddNode {\n readonly type: 'add';\n readonly base: number;\n readonly additive: readonly number[];\n readonly weight: number;\n}\n\n/**\n * Closed node union — exactly three variants (clip / blend / add). AI users\n * exhaustive `switch (node.type)` without default; TS guards completeness\n * (charter P3). No FSM / Mask / transition / BlendSpace variants are reserved\n * (OOS-1..4).\n */\nexport type AnimationGraphNode =\n | AnimationGraphClipNode\n | AnimationGraphBlendNode\n | AnimationGraphAddNode;\n\n/**\n * AnimationGraph asset POD — a Clip/Blend/Add DAG with per-node static weights.\n *\n * Joins the closed `Asset` union with `kind: 'animation-graph'` (AC-14); minted\n * into a `Handle<'AnimationGraph', 'shared'>` via `world.allocSharedRef` /\n * `AssetRegistry`, shared across multiple entities. Constructed via\n * `defineAnimationGraph` (runtime), which validates topology (no out-of-range\n * refs / cycles / invalid weights / empty graph) before a handle is minted.\n */\nexport interface AnimationGraph {\n readonly kind: 'animation-graph';\n readonly nodes: readonly AnimationGraphNode[];\n readonly root: number;\n}\n\n// === VideoAsset POD shape (feat-20260623-world-space-video-asset M1) ==========\n//\n// Decision anchors:\n// - requirements AC-01 (VideoAsset is Asset closed-union 15th member;\n// kind discriminator 'video'; payload { url: string }, no width/height/duration).\n// - requirements constraint: payload must not inline video bytes, only a URL descriptor.\n// - plan-strategy D-4 (VideoAsset descriptor naming aligns with TextureAsset/AudioClipAsset).\n// - charter F1 (AI users discover the schema via IDE autocomplete on the closed\n// `Asset` union + `Handle<VideoAsset>` returns from `AssetRegistry.register`).\n// - plan-strategy D-5 (resolveTexLike identifies video kind via `payload.kind === 'video'`;\n// video does not masquerade as 'texture').\n//\n// `refs` is always empty (isolated leaf) — VideoAsset carries no sub-asset\n// references (plan-strategy S6.3). The `url` field points to an external video\n// file; the runtime resolves it into an HTMLVideoElement via the host-provided\n// `VideoElementProvider` World Resource (plan-strategy D-1).\n\n/**\n * Video asset POD shape -- pure `{url}` descriptor.\n *\n * `VideoAsset` is a runtime-only asset kind (OOS-1: no import/cook pipeline).\n * The `url` field points to an external video file (e.g. `*.webm` / `*.mp4`);\n * the engine does NOT decode video bytes -- it delegates to the host-side\n * `HTMLVideoElement` via `VideoElementProvider` (plan-strategy D-1).\n *\n * `width` / `height` / `duration` are deliberately absent from the POD:\n * the runtime reads them from `HTMLVideoElement.videoWidth` /\n * `videoHeight` / `duration` after `loadedmetadata` fires (requirements\n * constraint \"payload must not inline video bytes\").\n *\n * Consumers reference a VideoAsset via a material texture value\n * fields (e.g. `baseColorTexture`), sharing the same `texture2d` slot with\n * static textures (charter P4 consistent abstraction). The extraction layer\n * (render-system-extract `resolveTexLike`) identifies the video kind and\n * routes to the per-frame transient texture pathway instead of the static\n * `GpuResourceStore.ensureResident` cache (plan-strategy D-5).\n */\nexport interface VideoAsset {\n readonly kind: 'video';\n readonly url: string;\n}\n\n/**\n * Skeleton asset POD shape — pure rig data, no mesh attachment.\n *\n * `inverseBindMatrices` is a Float32Array of length jointCount * 16\n * (column-major mat4 per joint). Missing IBM in source glTF is filled\n * with identity mat4 at importer time.\n *\n * `jointCount` is the number of joints (= IBM array length / 16).\n * Keys off the glTF skin's `joints[]` array length; validated against\n * MAX_JOINTS (256) at importer time.\n */\nexport interface SkeletonAsset {\n readonly kind: 'skeleton';\n readonly inverseBindMatrices: Float32Array;\n readonly jointCount: number;\n}\n\n/**\n * Skin sub-asset — the binding between a skeleton and a scene node hierarchy.\n *\n * `skeletonGuid` references a SkeletonAsset by GUID (string form).\n * `jointPaths` is a parallel array to the skeleton's joints; each entry\n * is a Name-component path from scene root to the joint entity, used\n * at post-spawn time to populate Skin.joints: Entity[].\n *\n * Zero-Entity-reference at the asset layer (AC-06): no Entity or\n * LocalEntityId fields — the binding is name-based, resolved at instantiate time.\n */\nexport interface SkinAsset {\n readonly kind: 'skin';\n readonly skeletonGuid: string;\n readonly jointPaths: readonly string[];\n}\n\n/**\n * VertexAttributeMap — 14-key closed set (feat-20260823 vertex-color asset closure).\n *\n * Canonical interleaved order (plan-strategy F-1, must match bridge + layout layers):\n * position / normal / uv / tangent / skinIndex / skinWeight / uv1..uv7 / color\n *\n * @location mapping per plan-strategy D-4:\n * position@0 normal@1 uv@2 tangent@3 skinIndex@4 skinWeight@5\n * uv1@6 uv2@7 uv3@8 uv4@9 uv5@10 uv6@11 uv7@12 color@13\n *\n * Keys align with Three.js r184 `BufferGeometry.attributes` naming (D-P1 +\n * plan-strategy §7.2 mental migration stance). Importers rename at ingest\n * (`POSITION -> position` / `TEXCOORD_0 -> uv` / `JOINTS_0 -> skinIndex` /\n * `WEIGHTS_0 -> skinWeight`) so the runtime key space remains lowercase.\n *\n * All 14 keys are optional; a mesh with only `position` (static unlit) is\n * valid. Values accept the three common binary shapes:\n * `ArrayBuffer | Float32Array | Uint16Array` (extend only via minor add per\n * the closed-union evolution contract).\n *\n * AC-15 narrowing: consumer sites writing\n * `for (const [key, buffer] of Object.entries(attributes))` observe `key`\n * typed as the 14-member literal union without `as` casts; any typo (e.g.\n * `'POSITION'`) is a TS compile-time error.\n */\nexport interface VertexAttributeMap {\n position?: ArrayBuffer | Float32Array | Uint16Array;\n normal?: ArrayBuffer | Float32Array | Uint16Array;\n uv?: ArrayBuffer | Float32Array | Uint16Array;\n tangent?: ArrayBuffer | Float32Array | Uint16Array;\n skinIndex?: ArrayBuffer | Float32Array | Uint16Array;\n skinWeight?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 1 (feat-20260629-multi-uv-set-support m3-w3, pre-added by M1 for bridge typecheck). */\n uv1?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 2 */\n uv2?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 3 */\n uv3?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 4 */\n uv4?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 5 */\n uv5?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 6 */\n uv6?: ArrayBuffer | Float32Array | Uint16Array;\n /** UV set 7 */\n uv7?: ArrayBuffer | Float32Array | Uint16Array;\n /** Optional per-vertex linear RGBA color, exactly four finite floats per vertex. */\n color?: Float32Array;\n}\n\n/** Closed storage vocabulary used by the geometry pack boundary. */\nexport type VertexAttributeStorage = 'array-buffer' | 'float32' | 'uint16' | 'other';\n\n/** Lossless detail for one rejected canonical vertex attribute pack. */\nexport type VertexAttributePackDetail =\n | {\n readonly field: 'vertexCount';\n readonly reason: 'vertex-count-invalid';\n readonly actual: number;\n }\n | {\n readonly field: 'attributes';\n readonly reason: 'attributes-empty';\n readonly actualCount: 0;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-storage-invalid';\n readonly expectedStorage: 'float32' | 'uint16';\n readonly actualStorage: VertexAttributeStorage;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-cardinality-mismatch';\n readonly vertexCount: number;\n readonly componentsPerVertex: number;\n readonly expectedLength: number;\n readonly actualLength: number;\n }\n | {\n readonly field: keyof VertexAttributeMap;\n readonly reason: 'attribute-non-finite';\n readonly elementIndex: number;\n readonly actual: 'nan' | 'positive-infinity' | 'negative-infinity';\n };\n\n/**\n * Canonical UV attribute key order (set 0 = `uv`, sets 1..7 = `uv1..uv7`),\n * matching the VertexAttributeMap declaration + @location numbering (D-4).\n * SSOT for \"how many UV sets does this attribute map carry\" so the import,\n * gpu-resource, and layout-derivation layers count identically (no drift).\n */\nexport const UV_ATTRIBUTE_KEYS = ['uv', 'uv1', 'uv2', 'uv3', 'uv4', 'uv5', 'uv6', 'uv7'] as const;\n\n/**\n * Structural input for the UV-set counters: any object that may carry the\n * canonical UV attribute keys. Both the loosely-typed mesh-attribute record\n * (`Record<string, unknown>`) and the closed `VertexAttributeMap` satisfy it,\n * so the import / gpu-resource / layout layers all call one counter.\n */\nexport type UvAttributeSource = Partial<Record<(typeof UV_ATTRIBUTE_KEYS)[number], unknown>>;\n\n/**\n * Total number of UV sets present in an attribute map: the highest populated\n * `uv`/`uv1..uv7` index + 1, or 0 when none are present. A populated key is one\n * whose value is a typed array / array buffer / number array (the binary shapes\n * a vertex attribute can take). This is the single counter all multi-UV layers\n * derive from (feat-20260629 F-4 DRY collapse).\n */\nexport function countUvSets(attrs: UvAttributeSource | undefined): number {\n if (attrs === undefined) return 0;\n for (let i = UV_ATTRIBUTE_KEYS.length - 1; i >= 0; i--) {\n // biome-ignore lint/style/noNonNullAssertion: bounded index on const tuple\n const v = attrs[UV_ATTRIBUTE_KEYS[i]!];\n if (\n v instanceof Float32Array ||\n v instanceof Uint16Array ||\n v instanceof ArrayBuffer ||\n Array.isArray(v)\n ) {\n return i + 1;\n }\n }\n return 0;\n}\n\n/**\n * Extra UV sets beyond set 0 (= `countUvSets - 1`, floored at 0). The\n * gpu-resource + register layers size the dynamic vertex stride from this.\n */\nexport function countExtraUvSets(attrs: UvAttributeSource | undefined): number {\n const total = countUvSets(attrs);\n return total > 0 ? total - 1 : 0;\n}\n\n// === Asset error model SSOT (feat-20260511-asset-system-v1 / D-P1 / w3) =========\n//\n// Decision anchors:\n// - requirements §G3 + AC-03 + AC-10 + AC-21 + §1 callout row 9 (4-member closed\n// `AssetErrorCode` independent from `RhiErrorCode`; `AssetError` class with\n// `.code / .expected / .hint / .message` four-field surface structurally\n// parallel to `RhiError` / `InspectorError` / `MetricError`)\n// - plan-strategy §2 D-P1 (`@forgeax/engine-types` single-file SSOT for\n// AssetErrorCode; independent closed union aligned with\n// MetricErrorCode / InspectorErrorCode precedent)\n// - plan-strategy §7.3 (per-code `.hint` string literals locked verbatim\n// below; any drift updates both this module and the test fixtures)\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — `switch (err.code)` exhaustive without `default:`) +\n// proposition 5 (consistent abstraction — structurally parallel to\n// `@forgeax/engine-rhi` `RhiError` surface)\n// - architecture-principles #1 SSOT (the 4 literals + class shape live here\n// once; M4 AssetRegistry / M3 Geometry factories / AGENTS.md §Error model\n// row all reference this module)\n\n/**\n * Closed `AssetErrorCode` union — 23 members (D-P1 + feat-20260518 D-1 minor\n * evolution + feat-20260520-skylight-ibl-cubemap 5 members +\n * feat-20260523 mesh-upload-fix 1 member +\n * feat-20260523-shader-template-instance-split M1-T02 1 member +\n * feat-20260526-material-asset-multipass-renderstate M1 1 member +\n * feat-20260603-asset-import-loader-injection M1 2 members +\n * feat-20260604-hdr-equirect-cube-importer-loader M2 1 member +\n * feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 3 members +\n * feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 1 member +\n * feat-20260707-texture-block-compression M5 1 member\n * (mipgen-unsupported-compressed-format);\n * requirements §G3 + AC-03 + AC-21 +\n * feat-20260518 AC-02 + bug-20260523 AC-01. The runtime-guard SSOT for the\n * current member count is the ASSET_ERROR_HINTS key-count test, not this prose.)\n * Exhaustive `switch (err.code)` needs no default fallback — TypeScript guards\n * union completeness at compile time (charter F2/P2 machine-readable union >\n * prose + P3 explicit failure).\n *\n * Domain-separated from `RhiErrorCode 'asset-not-registered'` (which is a\n * render-time registry lookup miss, 18-member closed union in\n * `@forgeax/engine-rhi/src/errors.ts`). The two unions cover disjoint\n * lifecycle phases — AI users face only these 22 alternatives on the\n * `assets.load(guid, kind)` / `world.sharedRefs.resolve(handle)` /\n * `assets.installDecoder(kind, decoder)` surface.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'asset-not-found'` | `AssetRegistry.get(handle)` returned no entry (handle never registered or registry was reset); charter P3 explicit failure. |\n * | `'asset-parse-failed'` | decoded bytes are not a valid image (PNG / JPG header corruption on the load path; dimensions <= 0 / segments < 1 on the procedural geometry constructor path — double semantics locked by requirements §9 \"constructor path\" extension). |\n * | `'asset-format-unsupported'` | URL content-type / magic bytes are neither PNG nor JPG (v1 scope; KTX2 / Basis / GLTF embedded textures deferred to M3+). |\n * | `'asset-fetch-failed'` | `fetch(url)` returned non-2xx, threw, or the URL was otherwise unreachable (404 / network / CORS surface). |\n * | `'asset-invalid-value'` | `register<MaterialAsset>(payload)` value validation fails the 3-tier validator (type-mismatch / extra-key / missing-required) — fail-fast at register entry. |\n * | `'cubemap-handle-missing'` | internal equirect-to-cubemap projection has no live cubemap for a `Skylight.equirect` handle; `.hint` points to loading an EquirectAsset + caps.rgba16floatRenderable. |\n * | `'invalid-source-format'` | image importer path when `.hdr` decode needs rgba16float / rgba32float. |\n * | `'load-failed'` | `AssetRegistry.load` when guid entry exists in catalog but file is inaccessible. |\n * | `'device-unsupported'` | GPU capability gate: `device.caps.rgba16floatRenderable` missing. |\n * | `'ibl-precompute-not-dispatched'` | `IblPipelineCache` when counter increments but `queue.submit` hasn't been called. |\n * | `'mesh-vertex-stride-mismatch'` | `register({ kind: 'mesh', ... })` vertices buffer is not evenly divisible by 12 floats per vertex (position vec3 + normal vec3 + uv vec2 + tangent vec4) or `maxIndex+1 !== vertexCount` — fail-fast at register entry (charter P3 structured failure; `.detail` carries `vertexCount` / `floatsPerVertex`). |\n| `'material-circular-inheritance'` | material resolve detected a cycle in the parent chain; `.hint` carries the full cycle path (e.g. \"A -> B -> A\") via `err.detail.cycle`. |\n * | `'loader-not-registered'` | `AssetRegistry.load` dispatched on `asset.kind` but no installed decoder exists for that kind; `.detail.kind` is the missing kind and `.detail.registeredKinds` lists the kinds currently wired. |\n * | `'asset-not-imported'` | `AssetRegistry.load` found the GUID in the catalog but its DDC is absent and no build-time publication exists; `.hint` points back to build-time pre-import rather than a runtime workaround. |\n * | `'texture-source-not-imported'` | `loadTextureAsset` received an uncooked source locator instead of a Pack v2 artifact; the runtime carries no source decoder. |\n */\nexport type AssetErrorCode =\n | 'asset-not-found'\n | 'asset-parse-failed'\n | 'asset-format-unsupported'\n | 'asset-fetch-failed'\n | 'catalog-source-unconfigured'\n | 'asset-invalid-value'\n | 'cubemap-handle-missing'\n | 'invalid-source-format'\n | 'load-failed'\n | 'device-unsupported'\n | 'ibl-precompute-not-dispatched'\n | 'mesh-vertex-stride-mismatch'\n // === 1 new code (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'material-shader-ref-broken'\n // === 1 new code (feat-20260526-material-asset-multipass-renderstate M1 / w6) ===\n | 'material-circular-inheritance'\n // === 2 new codes (feat-20260603-asset-import-loader-injection M1 / w1) ===\n | 'loader-not-registered'\n | 'asset-not-imported'\n // === 1 new code (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===\n | 'texture-source-not-imported'\n // === 1 new code (perf-20260706-raw-container-failfast) ===\n // A mesh/material/scene/skeleton/skin/animation-clip catalog row whose\n // packageUrl is still a raw source container (.glb/.gltf/.fbx), not an\n // importer-produced artifact (.bin/.pack.json). Like texture-source-not-imported\n // this is transport-eligible: the studio form lazily imports via the injected\n // ImportTransport; the shipped form fails fast. Distinct from the generic\n // asset-not-imported so it never masks the parent-missing breadcrumb.\n | 'source-not-imported'\n // === 3 new codes (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===\n | 'mesh-renderer-material-override-invalid'\n | 'mesh-renderer-material-override-overflow'\n | 'mesh-asset-submeshes-empty'\n | 'mesh-asset-material-slot-index-out-of-range'\n | 'mesh-submesh-index-range-out-of-bounds'\n // === 1 new code (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n // Tileset region rectangle out of atlas extent OR tile entry regionIndex out of\n // regions array bounds (single closed code per plan-strategy §D-6 first-error\n // ordering). 19 -> 20 baseline-restored.\n | 'tileset-region-index-out-of-range'\n // === 1 new code (feat-20260629-multi-uv-set-support M2 / m2-w5) ===\n // mesh-bin header v2 contract violation: version unknown, uvSetCount out of\n // [0,8], or stride/floatsPerVertex self-consistency check failed at encode\n // exit or decode entry (Fail Fast). Carries detail { version, uvSetCount,\n // stride }. .hint = 're-cook the asset via importer'.\n | 'mesh-bin-contract-violation'\n // === 1 new code (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===\n // Tile entry optional field (widthCells / heightCells / pivotX / pivotY /\n // collider) or top-level atlases / region.atlasIndex schema invariant\n // breached at register time. `.detail.field` carries the closed 7-variant\n // enum + `.scope?` is 'tile-entry' | 'tileset-asset' (plan-strategy §D-6;\n // charter P3 closed enum + AI-grep affordance). 20 -> 21 M1 net add.\n | 'tileset-tile-entry-malformed'\n // === 1 new code (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===\n | 'asset-invalidated'\n // === 1 new code (feat-20260707-texture-block-compression M5 / w35, D-9) ===\n // deriveRenderDataTexture fail-fast: a block-compressed `format` requested\n // RUNTIME mip generation (`mipmap:true` with no offline `mipLevelCount>1`\n // chain). Compressed formats are not render targets, so the mipmap blit\n // pipeline cannot generate their mips (F-7); the chain must be baked offline.\n // `.hint` carries the self-recovery (bake offline mips, or set the sidecar\n // `compressionMode:'none'`). A compressed texture whose mips are ALREADY in\n // `data` (mipLevelCount>1 from a KTX2 level chain) does NOT trip this gate.\n | 'mipgen-unsupported-compressed-format';\n\n/**\n * Structured asset error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) structurally parallel to `@forgeax/engine-rhi`\n * `RhiError` + `@forgeax/engine-remote` `InspectorError` + `MetricError`\n * (charter proposition 5 consistent abstraction; AGENTS.md \"Errors are\n * structured. Return Result, never throw for expected failures.\").\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'asset-fetch-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter proposition 4 explicit failure\n * red line).\n *\n * The `.message` field is auto-composed for human stack traces and carries\n * the same content as `.code` + `.expected` + `.hint`; AI users prefer\n * field access on the structured triple.\n *\n * @example AI-user exhaustive switch on the 22 members (no default fallback)\n * ```ts\n * import { AssetError, type AssetErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: AssetErrorCode): string {\n * switch (code) {\n * case 'asset-not-found': return 'ensure handle was registered before get()';\n * case 'asset-parse-failed': return 'check file integrity or geometry dimensions';\n * case 'asset-format-unsupported': return 'convert to PNG or JPG';\n * case 'asset-fetch-failed': return 'check url path or dev server';\n * case 'asset-invalid-value': return 'read err.hint / err.detail for the case-specific fix';\n * }\n * }\n * ```\n */\nexport class AssetError extends Error {\n readonly code: AssetErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<AssetErrorDetail>;\n\n constructor(args: {\n code: AssetErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<AssetErrorDetail>;\n }) {\n super(`[AssetError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'AssetError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals (plan-strategy §7.3 lock-in). Exported\n * so M3 Geometry factories / M4 AssetRegistry / tests consume the same\n * SSOT — any drift here updates both producer call sites and the\n * AGENTS.md §Error model table.\n *\n * The shape is a `Record<AssetErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter proposition 4 explicit failure).\n */\nexport const ASSET_ERROR_HINTS: Readonly<Record<AssetErrorCode, string>> = {\n 'asset-fetch-failed':\n 'check url path; verify dev server is running; in tests use data: URL fixture (data:image/png;base64,...)',\n 'catalog-source-unconfigured':\n 'call AssetRegistry.setCatalogSource(source) before enumerateCatalog(), then retry the operation',\n 'asset-parse-failed':\n 'check file bytes are not corrupted; for procedural geometry: verify all dimensions > 0 and segments >= 1',\n 'asset-format-unsupported':\n 'v1 supports png/jpg only; convert .bmp/.webp etc. via image tooling; gltf/glb supported via @forgeax/engine-gltf importer (forgeax-engine-remote-gltf import <gltf-or-glb>)',\n 'asset-not-found':\n 'handle id not in registry; verify register() was called before get(); inspect() returns all live handles',\n 'asset-invalid-value':\n 'a register-time value failed validation; read err.hint for the case-specific fix (e.g. clamp a MaterialAsset param to [0,1], or give a strip-topology MeshAsset an index buffer) and err.detail for the offending field/value',\n 'cubemap-handle-missing':\n 'the equirect-to-cubemap projection (internal to the render-system record arm) has no live cubemap for this Skylight; ensure Skylight.equirect references a loaded EquirectAsset handle and caps.rgba16floatRenderable is true',\n 'invalid-source-format':\n 'decode .hdr via @forgeax/engine-image first; supported formats are rgba16float and rgba32float',\n 'load-failed':\n 'source asset could not be loaded; check GUID validity and file accessibility in the pack-index catalog',\n 'device-unsupported':\n 'GPU device lacks required capability; check device.caps for rgba16float renderable feature',\n 'ibl-precompute-not-dispatched':\n 'check IblPipelineCache.runIblPrecompute is called inside the internal GpuResourceStore equirect-to-cubemap projection; counters must not increment before queue.submit (plan D-7 / N-3 AC-20 invariant)',\n 'mesh-vertex-stride-mismatch':\n 'use meshFromInterleaved (packages/runtime/src/geometry/box.ts) or expand vertices buffer to canonical 12F layout (position vec3 + normal vec3 + uv vec2 + tangent vec4)',\n // === 1 new hint (feat-20260523-shader-template-instance-split M1-T02) ===\n 'material-shader-ref-broken':\n 'the materialShader identifier (path or GUID) resolves to no registered shader; check ShaderRegistry for path identifiers or AssetRegistry for GUID sub-assets',\n // === 1 new hint (feat-20260526-material-asset-multipass-renderstate M1 / w6) ===\n 'material-circular-inheritance':\n 'circular parent chain detected; inspect parent handles — use err.detail.cycle to see the full path (e.g. \"A -> B -> A\")',\n // === 2 new hints (feat-20260603-asset-import-loader-injection M1 / w1) ===\n 'loader-not-registered':\n 'no loader registered for this asset kind; register it via engine.assets.loaders.register(loader) (the loader carries its own kind); err.detail.registeredKinds lists the kinds currently wired',\n 'asset-not-imported':\n 'GUID is in the catalog but its DDC artefact is missing and no ImportTransport is wired (shipped form never falls back to a runtime import); add the asset to the build-time pre-import step instead of importing at runtime',\n // === 1 new hint (feat-20260604-hdr-equirect-cube-importer-loader M2 / w4) ===\n 'texture-source-not-imported':\n 'texture source not imported yet; wire createDevImportTransport() in the studio form for dev lazy-import, or pre-import via the build-time pipeline',\n // === 1 new hint (perf-20260706-raw-container-failfast) ===\n 'source-not-imported':\n 'this mesh/material/scene sub-asset row still points at the raw source container (.glb/.gltf/.fbx); wire createDevImportTransport() for dev lazy-import (POST /__import), or pre-import via the build-time pipeline. The runtime does not parse raw containers at load time.',\n // === 3 new hints (feat-20260608-mesh-multi-section-primitive-multi-material-slot M1 / w2) ===\n 'mesh-renderer-material-override-invalid':\n 'a MeshRenderer.materials slot is stale or does not resolve to a MaterialAsset; the renderer inherited the MeshAsset default for that slot',\n 'mesh-renderer-material-override-overflow':\n 'MeshRenderer.materials contains entries beyond MeshAsset.materialSlots; extra overrides are ignored',\n 'mesh-asset-submeshes-empty':\n 'MeshAsset.submeshes must have at least one entry; every mesh must declare at least one submesh; check MeshAsset registration payload for empty submeshes array',\n 'mesh-asset-material-slot-index-out-of-range':\n 'MeshAsset submesh materialSlot must index MeshAsset.materialSlots; re-cook the mesh and inspect the offending submesh/slot topology',\n 'mesh-submesh-index-range-out-of-bounds':\n 'submesh indexOffset + indexCount exceeds the parent mesh index buffer length; check submesh index range bounds against MeshAsset.indices and MeshAsset.vertices; err.detail carries submeshIndex, indexOffset, indexCount, indexBufferLength, and meshAssetGuid',\n // === 1 new hint (feat-20260608-tilemap-object-layer-rendering M0 baseline rebuild) ===\n 'tileset-region-index-out-of-range':\n 'a TilesetAsset.regions[] rectangle escapes the atlas extent OR a TilesetAsset.tiles[].regionIndex points past TilesetAsset.regions.length; check regions[i] (x + width <= atlasWidth, y + height <= atlasHeight) and tiles[i].regionIndex in [0, regions.length); err.detail carries tilesetGuid, tileId, regionIndex, regionCount',\n // === 1 new hint (feat-20260608-tilemap-object-layer-rendering M1 schema extension) ===\n 'tileset-tile-entry-malformed':\n 'a TilesetTileEntry optional field is out of range (widthCells / heightCells in (0, 64], pivotX / pivotY in [0, 1], collider rect/polygon in normalized [0,1]^2 with rect.length === 4 and polygon.points.length >= 3) OR a top-level field is out of range (atlases.length >= 1, region.atlasIndex in [0, atlases.length)); engine fail-fast at register-time. read err.detail.field (closed enum) + err.detail.scope (tile-entry | tileset-asset) + err.detail.tileEntryIndex to locate the offending entry; switch (err.detail.field) covers the 7 variants exhaustively without default',\n // === 1 new hint (feat-20260621-asset-registry-robustness-invalidate-inflight-cach M2 / w4) ===\n 'asset-invalidated':\n 'The asset was invalidated during load; call AssetRegistry.load(guid, kind) again to retry with a fresh fetch',\n // === 1 new hint (feat-20260629-multi-uv-set-support M2 / m2-w5) ===\n 'mesh-bin-contract-violation':\n 're-cook the asset via importer; the .bin sidecar v4 contract is violated — inspect err.detail.reason and its expected/actual projection, stride, cardinality, and byte-length facts',\n // === 1 new hint (feat-20260707-texture-block-compression M5 / w35, D-9) ===\n 'mipgen-unsupported-compressed-format':\n 'compressed-texture mips must be baked offline (the GPU cannot generate mips for a non-render-target block format); re-cook with an offline mip chain, or set the sidecar .meta.json compressionMode:\"none\" (or mipmap:false) to keep runtime mip generation on an uncompressed texture',\n};\n\n// === Font error model SSOT (feat-20260531-world-space-msdf-text-rendering M2 / w6) ===\n//\n// Decision anchors:\n// - plan-strategy D-11 (two closed unions: FontErrorCode = build/load phase,\n// TextErrorCode = runtime layout phase; structured .code/.expected/.hint/.detail;\n// TOFU is rendering behaviour not an error — AC-14)\n// - requirements AC-15 (non-TTF -> FontErrorCode 'unsupported-font-format')\n// - requirements AC-16 (both unions in types/src/index.ts; exhaustive\n// switch(err.code) without default compiles)\n// - requirements AC-20 (font concurrency > 8 -> TextErrorCode\n// 'font-concurrency-exceeded')\n// - charter P3 (explicit failure: structured error > silent behaviour,\n// D-8 rejects silent LRU eviction for concurrency violation)\n//\n// Domain separation: FontErrorCode covers build-time bake failures and\n// load-time atlas/sampler resolution; TextErrorCode covers runtime glyph\n// layout and text rendering failures.\n\n/**\n * Closed `FontErrorCode` union — build-time bake + load-time resolution\n * errors (plan-strategy D-11).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'unsupported-font-format'` | bake receives non-TTF input (OTF / WOFF2); `.expected: 'ttf'` (AC-15) |\n * | `'font-atlas-missing'` | typed font load has missing/empty atlas texture GUID |\n * | `'font-atlas-corrupted'` | sidecar JSON parse failed or glyph metrics shape invalid |\n * | `'bake-failed'` | @zappar/msdf-generator call threw (wasm unavailable / internal error) |\n */\nexport type FontErrorCode =\n | 'unsupported-font-format'\n | 'font-atlas-missing'\n | 'font-atlas-corrupted'\n | 'bake-failed';\n\n/**\n * Closed `TextErrorCode` union — runtime glyph layout and text rendering\n * errors (plan-strategy D-11).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'font-concurrency-exceeded'` | > 8 distinct FontAsset handles active in one frame; `.expected: 8` (AC-20) |\n * | `'font-atlas-missing'` | glyph layout system resolved fontHandle but atlas texture is not yet uploaded |\n * | `'glyph-layout-failed'` | layout computation encountered unexpected state (empty common block, etc.) |\n */\nexport type TextErrorCode =\n | 'font-concurrency-exceeded'\n | 'font-atlas-missing'\n | 'glyph-layout-failed';\n\n/**\n * Structured font error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) in the style of {@link AssetError}.\n *\n * AI users consume via property access:\n * `switch (err.code) { case 'unsupported-font-format': ... err.expected ... }`\n * — never by parsing `.message`.\n */\nexport class FontError extends Error {\n readonly code: FontErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<Record<string, unknown>>;\n\n constructor(args: {\n code: FontErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<Record<string, unknown>>;\n }) {\n super(`[FontError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'FontError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Structured text error — four-field surface (`.code` / `.expected` /\n * `.hint` / `.message`) in the style of {@link AssetError}.\n *\n * AI users consume via property access:\n * `switch (err.code) { case 'font-concurrency-exceeded': ... err.hint ... }`\n * — never by parsing `.message`.\n */\nexport class TextError extends Error {\n readonly code: TextErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: Readonly<Record<string, unknown>>;\n\n constructor(args: {\n code: TextErrorCode;\n expected: string;\n hint: string;\n detail?: Readonly<Record<string, unknown>>;\n }) {\n super(`[TextError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'TextError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n// === AssetErrorDetail discriminated union (feat-20260523-shader-template-instance-split M1-T02) ===\n//\n// Introduced to type-narrow the AssetError.detail field for the new\n// 'material-shader-ref-broken' variant. Existing AssetErrorCode members\n// keep their Record<string, unknown> detail shapes; the union is\n// backward-compatible because the detail field on AssetError is optional.\n\n/**\n * Detail for `material-shader-ref-broken` — materialShader identifier\n * (path or GUID) could not be resolved to a registered shader.\n */\nexport interface AssetMaterialShaderRefBrokenDetail {\n readonly code: 'material-shader-ref-broken';\n readonly materialAssetGuid: string;\n readonly missingShaderId: string;\n readonly materialShaderPath?: string;\n}\n\n/**\n * Detail for `tileset-region-index-out-of-range` (feat-20260608 M0 baseline rebuild).\n *\n * Carries the offending tileset GUID + tile-entry index + the rejected\n * `regionIndex` + the live `regionCount` so AI consumers can pinpoint\n * the malformed payload field via property access (charter P3 / P4).\n *\n * Surfaced by `validateTilesetPayload` along two paths:\n * - region rectangle escapes the parent atlas extent\n * (regionIndex == the offending rectangle index).\n * - `tiles[i].regionIndex` >= `regions.length` (or negative)\n * (tileId encodes which entry; regionIndex carries the rejected value).\n */\nexport interface AssetTilesetRegionIndexOutOfRangeDetail {\n readonly code: 'tileset-region-index-out-of-range';\n readonly tilesetGuid: string;\n readonly tileId: number;\n readonly regionIndex: number;\n readonly regionCount: number;\n}\n\n/**\n * Detail for `tileset-tile-entry-malformed` (feat-20260608 M1 schema\n * extension; plan-strategy §D-6).\n *\n * Closed 7-variant `.field` enum locks the AI-grep affordance: switch\n * (detail.field) over the union compiles without default (charter P3).\n *\n * - `widthCells` -- `tiles[i].widthCells` out of `(0, 64]`.\n * - `heightCells` -- `tiles[i].heightCells` out of `(0, 64]`.\n * - `pivotX` -- `tiles[i].pivotX` out of `[0, 1]`.\n * - `pivotY` -- `tiles[i].pivotY` out of `[0, 1]`.\n * - `collider` -- `tiles[i].collider` schema invariant (rect.length !==\n * 4 / rect dimension out of `[0, 1]^2` / polygon.points.length < 3 /\n * any point out of `[0, 1]^2` / type discriminator outside the closed\n * 3-variant enum).\n * - `atlases` -- top-level `atlases.length < 1` (empty atlas list).\n * - `atlasIndex` -- `regions[i].atlasIndex` outside `[0, atlases.length)`.\n *\n * `.scope?` is `'tile-entry'` when the violation is in `tiles[i].*` (in\n * which case `.tileEntryIndex` carries the offending `tiles[]` index) and\n * `'tileset-asset'` when the violation is at the top level (atlases /\n * region atlasIndex).\n */\nexport interface AssetTilesetTileEntryMalformedDetail {\n readonly code: 'tileset-tile-entry-malformed';\n readonly field:\n | 'widthCells'\n | 'heightCells'\n | 'pivotX'\n | 'pivotY'\n | 'collider'\n | 'atlases'\n | 'atlasIndex';\n readonly scope?: 'tileset-asset' | 'tile-entry';\n readonly tileEntryIndex?: number;\n readonly tilesetGuid: string;\n readonly expected?: string;\n readonly hint?: string;\n}\n\n/**\n * Detail for `mesh-bin-contract-violation`.\n *\n * The mesh-bin header is a projection of the canonical geometry layout. Keep\n * the complete wire facts in the structured detail so recovery never depends\n * on parsing the human-facing `expected` / `actual` strings. `sourceKey` and\n * `reason` identify the owning payload and the failed invariant; the nested\n * snapshots preserve the lossless expected/actual cardinality facts.\n */\nexport type AssetMeshBinContractViolationReason =\n | 'header-truncated'\n | 'version-unsupported'\n | 'header-invalid'\n | 'projection-mismatch'\n | 'payload-length-mismatch'\n | 'metadata-invalid'\n | 'attribute-invalid'\n | 'payload-non-finite';\n\nexport interface AssetMeshBinContractFacts {\n readonly field?:\n | 'byteLength'\n | 'version'\n | 'projectionVersion'\n | 'mask'\n | 'stride'\n | 'digest'\n | 'vertexBytes'\n | 'indexBytes'\n | 'jsonBytes'\n | 'metadata'\n | 'attribute';\n readonly attribute?: keyof VertexAttributeMap;\n readonly elementIndex?: number;\n readonly expectedLength?: number;\n readonly actualLength?: number;\n readonly actualValue?: 'nan' | 'positive-infinity' | 'negative-infinity';\n readonly version?: number;\n readonly projectionVersion?: number;\n readonly mask?: number;\n readonly digest?: string;\n readonly stride?: number;\n readonly vertexCount?: number;\n readonly vertexBytes?: number;\n readonly indexCount?: number;\n readonly indexWidth?: number;\n readonly indexBytes?: number;\n readonly jsonBytes?: number;\n readonly byteLength?: number;\n}\n\nexport interface AssetMeshBinContractViolationDetail {\n readonly code: 'mesh-bin-contract-violation';\n readonly sourceKey: string;\n readonly reason: AssetMeshBinContractViolationReason;\n readonly expected: Readonly<AssetMeshBinContractFacts>;\n readonly actual: Readonly<AssetMeshBinContractFacts>;\n}\n\n/**\n * Discriminated detail union for AssetError, narrowed per AssetErrorCode.\n *\n * Variants:\n * - `material-shader-ref-broken` -- materialShader identifier (path or GUID)\n * could not be resolved to a registered shader; carries materialAssetGuid +\n * missingShaderId.\n * - `asset-invalid-value` -- a register-time value failed validation;\n * carries `{ field: string; got: unknown }`.\n * - `mesh-renderer-material-override-overflow` -- override entries exceed\n * slot count; carries `{ expectedCount, actualCount, meshAssetGuid }`.\n * - `mesh-asset-submeshes-empty` -- MeshAsset.submeshes is empty array;\n * carries `{ meshAssetGuid }`.\n * - `mesh-submesh-index-range-out-of-bounds` -- submesh index range exceeds\n * parent mesh index buffer; carries `{ submeshIndex, indexOffset,\n * indexCount, indexBufferLength, meshAssetGuid }`.\n */\nexport type AssetErrorDetail =\n | import('./asset.js').AssetCodecFailureDetail\n | AssetMaterialShaderRefBrokenDetail\n | AssetTilesetRegionIndexOutOfRangeDetail\n | AssetTilesetTileEntryMalformedDetail\n | AssetMeshBinContractViolationDetail\n | VertexAttributePackDetail\n | { readonly field: string; readonly got: unknown }\n | { readonly field: string; readonly value: unknown; readonly reason: string }\n | { readonly expectedCount: number; readonly actualCount: number; readonly meshAssetGuid: string }\n | { readonly meshAssetGuid: string; readonly slotIndex: number; readonly handle: number }\n | {\n readonly meshAssetGuid: string;\n readonly slotIndex: number;\n readonly slotName: string;\n readonly defaultMaterialGuid: string;\n readonly actualKind: string;\n }\n | MeshMaterialOverrideConflict\n | {\n readonly meshAssetGuid: string;\n readonly submeshIndex: number;\n readonly materialSlot: number;\n readonly materialSlotCount: number;\n }\n | { readonly meshAssetGuid: string }\n | {\n readonly submeshIndex: number;\n readonly indexOffset: number;\n readonly indexCount: number;\n readonly indexBufferLength: number;\n readonly meshAssetGuid: string;\n }\n // Pre-existing detail shapes used by AssetRegistry / loaders / pipeline-builder\n // (added in M5 / w27 alongside the count-mismatch tightening so the union\n // accommodates every current call site without losing structural narrowing).\n | { readonly sourcePath: string }\n | { readonly kind: string; readonly registeredKinds?: readonly string[] }\n | { readonly key: string; readonly legalPattern: string }\n | { readonly passCount: number }\n | {\n readonly passIndex: number;\n readonly shaderKey: string;\n readonly cause: string;\n }\n | { readonly paramName: string; readonly expectedType: string; readonly got: unknown }\n | { readonly paramName: string; readonly got: unknown }\n | { readonly missingParams: readonly string[] }\n | { readonly cycle: string }\n | {\n readonly localId: number;\n readonly component: string;\n readonly field: string;\n readonly index: number;\n readonly refsLength: number;\n }\n | { readonly vertexCount: number; readonly floatsPerVertex: number }\n // feat-20260622 verify r1: sub-asset load-failure breadcrumb in structured\n // form. The recursive loader composes the same provenance into the `.hint`\n // string; this variant additionally exposes it for property access so AI\n // users locate the broken edge without parsing the hint (charter P3,\n // requirements section error-self-recovery). `sourceField`/`sceneEntityId`\n // mirror the originating AssetRef edge; both undefined for transitive\n // (texture) edges with no per-entity origin (D-2).\n | {\n readonly referencedByGuid: string;\n readonly referencedByKind: string;\n readonly subAssetGuid: string;\n readonly sceneEntityId?: number;\n readonly sourceField?: {\n readonly componentName?: string;\n readonly fieldName: string;\n readonly arrayIndex?: number;\n };\n };\n\n// === Image importer error model SSOT ===\n// ImageErrorDetailByCode owns the closed vocabulary and payload shapes. The\n// envelope and runtime constructors are derived from it so `.code` and\n// `.detail` stay correlated for consumers.\n\n/** Closed code union, derived from the detail map below. */\ninterface ImageErrorDetailByCode {\n 'image-decode-failed': {\n readonly reason: string;\n readonly path?: string;\n };\n 'image-format-unsupported': {\n readonly actualMime: string;\n readonly path?: string;\n readonly formatColorSpaceConflict?: {\n readonly format: string;\n readonly colorSpace: 'srgb' | 'linear';\n readonly expected: 'srgb' | 'linear';\n };\n };\n 'image-dimension-out-of-bounds': {\n readonly requested: { readonly width: number; readonly height: number };\n readonly limit: number;\n };\n 'image-meta-missing': {\n readonly sourcePath: string;\n readonly expectedSidecarPath: string;\n };\n 'image-hdr-decode-failed': {\n readonly reason: string;\n readonly path?: string;\n };\n 'atlas-empty-input': {\n readonly receivedCount: number;\n };\n 'atlas-size-exceeded': {\n readonly name: string;\n readonly width: number;\n readonly height: number;\n readonly maxAtlasSize: number;\n };\n 'atlas-region-mismatch': {\n readonly name: string;\n readonly regionsTotalPixels: number;\n readonly atlasPixels: number;\n };\n}\n\nexport type ImageErrorCode = keyof ImageErrorDetailByCode;\n\n/** Detail union projected from one code-to-payload map. */\nexport type ImageErrorDetailFor<C extends ImageErrorCode> = Readonly<{ code: C }> &\n ImageErrorDetailByCode[C];\n\nexport type ImageErrorDetail = {\n [C in ImageErrorCode]: ImageErrorDetailFor<C>;\n}[ImageErrorCode];\n\n/**\n * Correlated error envelope. `ImageErrorFor<C>` is the producer-facing\n * generic; `ImageError` is its closed union for exhaustive consumer switches.\n */\nexport type ImageErrorFor<C extends ImageErrorCode> = Error & {\n readonly code: C;\n readonly expected: string;\n readonly hint: string;\n readonly detail: ImageErrorDetailFor<C>;\n};\n\nexport type ImageError = {\n [C in ImageErrorCode]: ImageErrorFor<C>;\n}[ImageErrorCode];\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy section 2.3 / Tier 2\n * documentation). `Record<ImageErrorCode, string>` ensures compile-time\n * completeness; any future minor add to `ImageErrorCode` raises a TS error\n * on this map until the matching hint is supplied (charter proposition 4\n * explicit failure -- producer/consumer + reviewer all see the missing arm).\n *\n * Each hint embeds an executable command so AI users self-recover by\n * copy-pasting the hint into the shell (plan-strategy Tier 2 \"hint must\n * carry forgeax-engine-remote-image import <path>\" or similar; the image\n * plugin bin lands in feat-future-console-plugin-image — until then the\n * hint references the in-package importer surface).\n */\nexport const IMAGE_ERROR_HINTS: Readonly<Record<ImageErrorCode, string>> = {\n 'image-decode-failed':\n 'check file integrity; re-export from DCC tool (Photoshop / GIMP / Aseprite); dimensions > 0 + valid PNG / JPG header bytes',\n 'image-format-unsupported':\n 'supports PNG / JPG / TGA true-color sources; convert unsupported formats with: magick convert <input> <output>.png; check importSettings.colorSpace consistency with format family if formatColorSpaceConflict present',\n 'image-dimension-out-of-bounds':\n 'downscale source under device caps (typical maxTextureDimension2D = 8192 / 16384); use mipmap chain instead of larger source if lod is the goal',\n 'image-meta-missing': 'run: forgeax-engine-remote-asset import <path>',\n 'image-hdr-decode-failed':\n 'check .hdr file integrity; verify Radiance RGBE header magic (#?RADIANCE) and FORMAT=32-bit_rle_rgbe header field; ensure file was not truncated',\n // feat-20260521-sprite-atlas-animation M1 T-02 — atlas hook hint strings\n // (plan-strategy section 2 D-2). Each hint embeds an executable recovery\n // path so AI users self-repair by copy-pasting the hint into the shell\n // or into the build config (charter P3 explicit failure + AGENTS.md\n // Error model \"hint must carry executable recovery\").\n 'atlas-empty-input':\n 'verify forgeax-engine-remote-asset atlas --input <glob> --name <prefix> --output <dir> matches at least 1 PNG on disk; run `ls <glob>` to inspect the resolved file set; add the missing sprite source or fix the glob pattern',\n 'atlas-size-exceeded':\n 'downscale the source PNG so width * height <= maxAtlasSize^2 (default 4096); or split sprites across multiple atlas runs (forgeax-engine-remote-asset atlas --input <subset-glob> --name <other-prefix> --output <dir>); or raise the cap via --max-atlas-size 8192 if device caps allow it',\n 'atlas-region-mismatch':\n 'shelfPack returned regions exceeding atlas footprint — packer safety net; file a forgeax-engine bug; rerun forgeax-engine-remote-asset atlas with a smaller input set or lower --max-atlas-size as temporary recovery',\n};\n\n/**\n * Image color-space discriminator (plan-strategy section 2.5 D Open Q-4 (c)).\n *\n * `'srgb'` -- baseColor / albedo authored in sRGB display space; uploaded\n * with `format='*-srgb'` so hardware applies the gamma decode automatically\n * (research F-3 spec guarantee for mipmap blits).\n *\n * `'linear'` -- normal / metallic / roughness / data textures authored in\n * linear color space; uploaded with `format='*-unorm'` (no gamma transform).\n */\nexport type ImageColorSpace = 'srgb' | 'linear';\n\n/**\n * Image importer settings POD (plan-strategy section 2.2 D-4 image disk\n * schema; AC-26). 5-field free-form object persisted into the `*.meta.json`\n * `importSettings` field; the GUID lives at the top of the POD so consumers\n * (importer + runtime + console asset import) share one schema (charter\n * proposition 5 consistent abstraction).\n *\n * Fields:\n * - `guid` -- string-form RFC 4122 dash-form UUID identifying the single\n * image sub-asset (image disk schema is currently single-sub-asset by\n * design; cubemap face / array layer reserved for future feat).\n * - `colorSpace` -- `'srgb' | 'linear'` (drives uploadTexture format\n * selection; plan-strategy section 2.5).\n * - `mipmap` -- `'auto' | 'none'` (`'auto'` enables runtime mipmap-generator\n * blit chain; `'none'` ships a single mip level).\n * - `addressMode` -- WGPU address mode for sampler (passed through to\n * uploadTexture's sampler descriptor).\n * - `filterMode` -- magFilter / minFilter selector.\n *\n * The shape stays free-form `Record<string, unknown>` compatible at the\n * `*.meta.json` `importSettings` slot (research F-9 -- meta.schema.json\n * does not lock importSettings sub-shape; minor add of new fields is\n * non-breaking; plan-strategy R5 risk-free).\n */\nexport interface ImageMeta {\n readonly guid: string;\n readonly colorSpace: ImageColorSpace;\n readonly mipmap: 'auto' | 'none';\n readonly addressMode: 'repeat' | 'clamp-to-edge' | 'mirror-repeat';\n readonly filterMode: 'nearest' | 'linear';\n /** Optional asset-owned cooked-payload target dimension. */\n readonly downscaleMaxDimension?: number;\n}\n\n/**\n * Decoded image POD (plan-strategy section 2.2 + section 3.3; AC-26).\n * Producer: `@forgeax/engine-image` parseImage / decodeImageFromFile.\n * Consumer: `@forgeax/engine-runtime` AssetRegistry.uploadTexture (M3).\n *\n * Six-field tight-packed shape:\n * - `bytes` -- decoded pixel buffer (RGBA tight-packed; 4 bytes per pixel).\n * Producer guarantees `bytes.length === width * height * 4`.\n * - `width` / `height` -- pixel dimensions (must satisfy device caps\n * `maxTextureDimension2D` floor; surfaced as `image-dimension-out-of-bounds`\n * when exceeded).\n * - `mime` -- discriminator over the supported set (`'image/jpeg' | 'image/png' | 'image/x-tga'`);\n * keeps the runtime side from sniffing magic bytes.\n * - `colorSpace` -- carried over from `ImageMeta.colorSpace`; uploadTexture\n * asserts `format <-> colorSpace` consistency at the GPU upload entry\n * (plan-strategy section 2.5 D Open Q-4 (c)).\n * - `mipmap` -- boolean derived from `ImageMeta.mipmap === 'auto'`; the\n * runtime mipmap-generator skips the blit chain when false.\n *\n * Math-free POD; no Float32Array / branded handle on this shape (charter\n * proposition 5 consistent abstraction with TextureAsset POD).\n */\nexport interface DecodedImage {\n readonly bytes: Uint8Array;\n readonly width: number;\n readonly height: number;\n readonly mime: 'image/jpeg' | 'image/png' | 'image/x-tga';\n readonly colorSpace: ImageColorSpace;\n readonly mipmap: boolean;\n}\n\n// === AssetGuid — disk-layer GUID brand type (feat-20260513-guid-asset-package-system) ===========\n//\n// Type-only declaration. Implementation (parse / format / equals / random) lives in\n// @forgeax/engine-pack/guid. The brand field is a phantom string literal that prevents\n// accidental assignment from plain Uint8Array or string at compile time.\n\n/** 16-byte UUID brand for disk-layer asset identification. RFC 4122 UUIDv7 wire form. */\nexport type AssetGuid = Uint8Array & { readonly __guidBrand: 'AssetGuid' };\n\n// === PackErrorCode / PackErrorDetail — disk-layer error SSOT (feat-20260513-guid-asset-package-system w15) ===\n//\n// Decision anchors:\n// - requirements §6.1 (13-member closed union literal set SSOT; widened from\n// the original 8 by feat-20260523-shader-template-instance-split (+1) and\n// feat-20260608-scene-nesting-ecs-fication M1 / w8 (+4))\n// - requirements §6.2 AC-05/07 (per-code discriminated detail)\n// - plan-strategy §D-5 (PackError 4-field surface + check-pack-error-detail-narrowed.mjs guard)\n// - AGENTS.md §Error model (structurally parallel to AssetErrorCode / InspectorErrorCode)\n\n/**\n * Closed PackErrorCode union — 15 members.\n * Used exclusively by the @forgeax/engine-pack scanner fail-fast chain.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'pack-malformed-meta'` | .meta.json fails ajv schema validation |\n * | `'pack-malformed-pack'` | .pack.json fails ajv schema validation |\n * | `'pack-guid-malformed'` | a GUID field is not a valid 36-char RFC 4122 dash-form string |\n * | `'pack-orphan-meta'` | .meta.json exists but the corresponding source file does not |\n * | `'pack-meta-missing'` | source file exists but no .meta.json (strict mode) |\n * | `'pack-guid-collision'` | two .pack.json files declare the same GUID |\n * | `'pack-cyclic-reference'` | asset refs[] (incl. mount.source) form a cycle |\n * | `'pack-subasset-index-out-of-range'` | subAsset.sourceIndex >= source count |\n * | `'payload-schema-mismatch'` | material payload fails materialShader / paramSchema schema |\n * | `'pack-mount-localid-overlap'` | mount memberFirst windows overlap or collide with entities[] |\n * | `'pack-mount-count-mismatch'` | mount memberCount disagrees with referenced child SceneAsset |\n * | `'pack-mount-override-localid-out-of-range'` | override.localId outside the mount's member window |\n * | `'pack-mount-override-unknown-field'` | override.comp / override.field unknown to the schema vocab |\n * | `'pack-unknown-path'` | @name references a name not declared in package.json#forgeax.assets.paths |\n * | `'pack-malformed-path-ref'` | source starts with @ but does not match @<name>/<rest> format, or resolves to a path outside the declared directory |\n *\n * Membership history: 8 -> 9 added 'payload-schema-mismatch' (feat-20260523\n * shader-template-instance-split); 9 -> 13 adds the four mount-* codes\n * (feat-20260608-scene-nesting-ecs-fication M1 / w8, plan-strategy D-8 literals\n * locked); 13 -> 15 adds 'pack-unknown-path' + 'pack-malformed-path-ref'\n * (feat-20260625-asset-meta-source-mount-prefix M1 / w1).\n */\nexport type PackErrorCode =\n | 'pack-malformed-meta'\n | 'pack-malformed-pack'\n | 'pack-guid-malformed'\n | 'pack-orphan-meta'\n | 'pack-meta-missing'\n | 'pack-guid-collision'\n | 'pack-cyclic-reference'\n | 'pack-subasset-index-out-of-range'\n // === 1 new code (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'payload-schema-mismatch'\n // === 4 new codes (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8 literals locked) ===\n | 'pack-mount-localid-overlap'\n | 'pack-mount-count-mismatch'\n | 'pack-mount-override-localid-out-of-range'\n | 'pack-mount-override-unknown-field'\n // === 2 new codes (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n | 'pack-unknown-path'\n | 'pack-malformed-path-ref';\n\n/**\n * Discriminated detail union for PackError — narrowed per PackError.code.\n * AI users access `err.detail.<field>` directly after switch (err.code) narrows\n * the variant. Variants without an own `code` field are narrowed exclusively by\n * the top-level `PackError.code` discriminant (the legacy 7 variants below);\n * variants that carry a `code` field (`payload-schema-mismatch`, the evolved\n * `pack-cyclic-reference`, and the four mount-* additions) double-narrow via\n * `Extract<PackErrorDetail, { code: ... }>` at the type layer (R10).\n *\n * Structurally parallel to RhiErrorDetail / MetricErrorDetail.\n */\nexport type PackErrorDetail =\n | {\n /** Absolute or relative path to the malformed .meta.json file. */\n readonly path: string;\n /** ajv validation errors produced by validateMeta(). */\n readonly ajvErrors: readonly { readonly instancePath: string; readonly message: string }[];\n }\n | {\n /** Absolute or relative path to the malformed .pack.json file. */\n readonly path: string;\n /** ajv validation errors produced by validatePack(). */\n readonly ajvErrors: readonly { readonly instancePath: string; readonly message: string }[];\n /**\n * Optional human-readable reason category. Set to a fixed literal for\n * the runtime instantiate-path SceneEntity field-name typo route:\n * `'unknown component field'` (requirements §AC-08(b)). Absent on\n * scanner-path ajv-validation failures (where the structural\n * ajvErrors[].message string already carries the diagnostic).\n */\n readonly reason?: string;\n }\n | {\n /** The raw string value that failed UUID validation. */\n readonly raw: string;\n /** Human-readable reason (e.g. 'expected 36-char RFC 4122 dash-form UUID'). */\n readonly reason: string;\n }\n | {\n /** Path of the .meta.json that has no corresponding source file. */\n readonly metaPath: string;\n /** Path that was expected to exist as a source file. */\n readonly expectedFile: string;\n }\n | {\n /** Path of the source file that has no accompanying .meta.json. */\n readonly filePath: string;\n }\n | {\n /** The two .pack.json paths that both declare the same GUID (tuple, always length 2). */\n readonly paths: readonly [string, string];\n /** The colliding GUID dash-form string. */\n readonly guid: string;\n }\n // === Evolved variant (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy R10): pack-cyclic-reference now carries `code` + `kind` so\n // the build-time scanner (kind: 'mount-asset', cycle: GUID list) and the\n // runtime ChildOf detector (kind: 'childof', cycle: LocalEntityId list) stay\n // narrowable from a single error code. ===\n | {\n readonly code: 'pack-cyclic-reference';\n /**\n * Cycle origin tag: 'childof' for runtime ChildOf relationship cycles\n * (LocalEntityId stringified), 'mount-asset' for build-time\n * SceneAsset.mounts[].source GUID cycles (D-1).\n */\n readonly kind: 'childof' | 'mount-asset';\n /** Cycle path as ordered identifier strings; first === last. */\n readonly cycle: readonly string[];\n }\n | {\n /** Path of the .meta.json declaring the out-of-range sourceIndex. */\n readonly metaPath: string;\n /** The declared sourceIndex value. */\n readonly sourceIndex: number;\n /** The maximum valid sourceIndex (exclusive upper bound = source count). */\n readonly max: number;\n }\n // === 1 new variant (feat-20260523-shader-template-instance-split M1-T02) ===\n | {\n /** Discriminated code for material payload schema mismatch. */\n readonly code: 'payload-schema-mismatch';\n /** GUID of the offending material asset. */\n readonly guid: string;\n /** ajv validation errors for the material payload. */\n readonly errors: readonly { readonly instancePath: string; readonly message: string }[];\n }\n // === 4 new variants (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8 literals locked; AC-04 / AC-05 / AC-06 / AC-07) ===\n | {\n readonly code: 'pack-mount-localid-overlap';\n /** Overlapping LocalEntityId values (sorted ascending). */\n readonly overlapping: readonly number[];\n /**\n * Source labels for the conflicting windows. Each entry is a\n * human-readable origin string (`mount[<localId>]`,\n * `entities[<localId>]`, etc.) of length matching `overlapping[]`.\n */\n readonly sources: readonly string[];\n }\n | {\n readonly code: 'pack-mount-count-mismatch';\n /** localId of the offending mount within its parent SceneAsset. */\n readonly mountLocalId: number;\n /** memberCount declared on the mount. */\n readonly declared: number;\n /** Actual entities[].length resolved from the referenced child SceneAsset. */\n readonly actual: number;\n }\n | {\n readonly code: 'pack-mount-override-localid-out-of-range';\n /** override.localId that fell outside the mount's member window. */\n readonly overrideLocalId: number;\n /** localId of the parent mount. */\n readonly mountLocalId: number;\n /** memberCount of the parent mount (window upper bound, exclusive). */\n readonly memberCount: number;\n }\n | {\n readonly code: 'pack-mount-override-unknown-field';\n /** Component name on which the override was authored. */\n readonly comp: string;\n /** Unknown field name. */\n readonly field: string;\n /** localId of the parent mount carrying the override. */\n readonly mountLocalId: number;\n }\n // === 2 new variants (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n | {\n readonly code: 'pack-unknown-path';\n /** The @name that was not found in package.json#forgeax.assets.paths. */\n readonly pathName: string;\n /** All known path names declared in package.json#forgeax.assets.paths. */\n readonly knownNames: readonly string[];\n }\n | {\n readonly code: 'pack-malformed-path-ref';\n /**\n * Which malformation occurred, so an AI user can branch on a property\n * instead of parsing the human-facing .hint:\n * - 'format': source starts with @ but does not match @<name>/<rest>\n * - 'escape': rest segment resolves outside the declared path directory\n */\n readonly reason: 'format' | 'escape';\n /** The raw source string as written in the .meta.json. */\n readonly rawSource: string;\n /** The expected format description for self-correction. */\n readonly expectedFormat: string;\n };\n\n/**\n * Per-code .hint string literals SSOT.\n * Record<PackErrorCode, string> ensures compile-time completeness.\n */\nexport const PACK_ERROR_HINTS: Readonly<Record<PackErrorCode, string>> = {\n 'pack-malformed-meta':\n 'check guid is a valid RFC 4122 UUID; validate with: ajv validate -s schema/meta.schema.json -d <file>',\n 'pack-malformed-pack':\n 'check all asset guid and refs[] fields are 36-char dash-form UUIDs; validate with pack.schema.json',\n 'pack-guid-malformed':\n 'use AssetGuid.random() or a UUIDv7 generator; all GUID fields must be 36-char RFC 4122 dash-form',\n 'pack-orphan-meta': 'remove the orphan .meta.json or add the missing source file next to it',\n 'pack-meta-missing':\n 'run forgeax-engine-remote-asset scan --roots <dir> to list source files without .meta.json',\n 'pack-guid-collision':\n 'run forgeax-engine-remote-asset verify to list all GUID collisions; each GUID must be globally unique',\n 'pack-cyclic-reference':\n 'run forgeax-engine-remote-asset verify to print the cycle path; break the cycle by removing a refs[] entry',\n 'pack-subasset-index-out-of-range':\n 'check subAssets[].sourceIndex does not exceed the actual sub-image count in the source file',\n // === 1 new hint (feat-20260523-shader-template-instance-split M1-T02) ===\n 'payload-schema-mismatch':\n 'material asset payload failed schema validation; check paramSchema entries all use valid types from MATERIAL_PARAM_TYPES and materialShader is a non-empty string',\n // === 4 new hints (feat-20260608-scene-nesting-ecs-fication M1 / w8;\n // plan-strategy D-8) ===\n 'pack-mount-localid-overlap':\n 'check parent SceneAsset.mounts[].memberFirst windows do not overlap with each other or with entities[].localId; rebuild mount sidecar after the child SceneAsset reimport',\n 'pack-mount-count-mismatch':\n 'mount.memberCount must equal the referenced child SceneAsset totalSlots (entities.length + sum(mounts[].memberCount) + mounts.length); rebuild mount sidecar via forgeax-engine-remote-asset verify <dir> after the child SceneAsset reimport',\n 'pack-mount-override-localid-out-of-range':\n 'override.localId must be in [0, mount.memberCount); shrink the override or extend memberCount to match the child SceneAsset',\n 'pack-mount-override-unknown-field':\n 'override.comp / override.field must match a defined component schema; check defineComponent registry or rebuild mount sidecar after the child SceneAsset reimport',\n // === 2 new hints (feat-20260625-asset-meta-source-mount-prefix M1 / w1) ===\n 'pack-unknown-path':\n 'the @name in source is not declared in package.json#forgeax.assets.paths; add it there or use a known name from the list in error.detail.knownNames',\n 'pack-malformed-path-ref':\n 'source must be @<name>/<rest> where name is a key in package.json#forgeax.assets.paths; the resolved path must not escape the declared directory',\n};\n\n// === AudioErrorCode / AudioError / AudioErrorDetail -- audio error SSOT (feat-20260527-audio-system M1 / w4) ===\n//\n// Decision anchors:\n// - requirements S-8 (5-member independent closed union: context-creation-failed /\n// decode-failed / context-suspended / invalid-clip-handle / bus-not-found)\n// - requirements AC-13 (AudioErrorCode closed union switch exhaustiveness)\n// - plan-strategy D-7 (AudioErrorCode SSOT in engine-types, parallel to\n// ImageErrorCode / GltfErrorCode / AssetErrorCode)\n// - plan-strategy section 8 AI User Affordance (structured 4-field surface:\n// .code / .expected / .hint / .detail)\n// - charter P3 (explicit failure: switch (err.code) exhaustive without default;\n// .hint provides concrete recovery action)\n// - charter P4 (consistent abstraction: structurally parallel to AssetError,\n// ImageError, GltfError same 4-field shape)\n// - architecture-principles #1 SSOT (the 5 literals + class shape + hints table\n// live here once; engine-audio package references this module)\n\n/**\n * Closed `AudioErrorCode` union -- 5 members (plan-strategy D-7;\n * requirements S-8). Exhaustive `switch (err.code)` needs no default\n * fallback -- TypeScript guards union completeness at compile time\n * (charter P3 explicit failure).\n *\n * Domain-separated from `AssetErrorCode` (runtime registry surface, 12 members)\n * and `GltfErrorCode` (importer surface, 13 members). AI users face these 5\n * alternatives at the audio engine surface (`@forgeax/engine-audio`\n * AudioError + `@forgeax/engine-audio-webaudio` backend).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'context-creation-failed'` | `new AudioContext()` threw or returned null (privacy browser / no audio device) |\n * | `'decode-failed'` | `decodeAudioData(arrayBuffer)` rejected (corrupt file / unsupported codec) |\n * | `'context-suspended'` | `play()` called while AudioContext.state is `'suspended'` and gesture listener failed to resume |\n * | `'invalid-clip-handle'` | AudioSource.clip handle is dangling or refers to an unregistered asset |\n * | `'bus-not-found'` | AudioSource.bus refers to a string literal outside the `'sfx' | 'music'` closed set |\n */\nexport type AudioErrorCode =\n | 'context-creation-failed'\n | 'decode-failed'\n | 'context-suspended'\n | 'invalid-clip-handle'\n | 'bus-not-found';\n\n/**\n * Per-code `AudioError` detail shapes -- discriminated payloads narrowed\n * by `AudioError.code` so AI users writing `switch (err.code)` get\n * control-flow-tightened access to the relevant detail fields\n * (charter P3 explicit failure).\n */\n\n/** `context-creation-failed` payload: carries the original error reason. */\nexport interface AudioCtxCreationFailedDetail {\n readonly code: 'context-creation-failed';\n readonly reason: string;\n}\n\n/** `decode-failed` payload: carries the original decode error reason. */\nexport interface AudioDecodeFailedDetail {\n readonly code: 'decode-failed';\n readonly reason: string;\n}\n\n/** `context-suspended` payload: empty marker detail (no extra fields). */\nexport interface AudioCtxSuspendedDetail {\n readonly code: 'context-suspended';\n}\n\n/** `invalid-clip-handle` payload: carries the dangling handle identifier. */\nexport interface AudioInvalidClipHandleDetail {\n readonly code: 'invalid-clip-handle';\n readonly clipHandleId: number;\n}\n\n/** `bus-not-found` payload: carries the invalid bus name attempted. */\nexport interface AudioBusNotFoundDetail {\n readonly code: 'bus-not-found';\n readonly attemptedBus: string;\n}\n\n/**\n * Discriminated detail union for `AudioError`, narrowed per `AudioError.code`.\n * AI users obtain the concrete detail shape via `switch (err.code)` without\n * needing a fallback `as` cast (charter P3).\n */\nexport type AudioErrorDetail =\n | AudioCtxCreationFailedDetail\n | AudioDecodeFailedDetail\n | AudioCtxSuspendedDetail\n | AudioInvalidClipHandleDetail\n | AudioBusNotFoundDetail;\n\n/**\n * Structured audio error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `@forgeax/engine-types`\n * `AssetError` + `ImageError` + `GltfError` same-shape errors\n * (charter P4 consistent abstraction; AGENTS.md \"Errors are structured.\n * Return Result, never throw for expected failures.\").\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'decode-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter P3 explicit failure red line).\n *\n * The `.message` field is auto-composed for human stack traces and carries\n * the same content as `.code` + `.expected` + `.hint`; AI users prefer\n * field access on the structured triple.\n *\n * @example AI-user exhaustive switch on the 5 members (no default fallback)\n * ```ts\n * import { AudioError, type AudioErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: AudioErrorCode): string {\n * switch (code) {\n * case 'context-creation-failed': return 'check browser supports AudioContext';\n * case 'decode-failed': return 'ensure audio file is a valid wav/mp3/ogg/flac';\n * case 'context-suspended': return 'call play after user gesture to trigger resume';\n * case 'invalid-clip-handle': return 'verify clip was registered via AssetRegistry';\n * case 'bus-not-found': return 'use sfx or music bus literal';\n * }\n * }\n * ```\n */\nexport class AudioError extends Error {\n readonly code: AudioErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: AudioErrorDetail;\n\n constructor(args: {\n code: AudioErrorCode;\n expected: string;\n hint: string;\n detail?: AudioErrorDetail;\n }) {\n super(`[AudioError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'AudioError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy D-7 lock-in).\n * Exported so engine-audio error helpers and tests consume the same SSOT\n * -- any drift here updates both producer call sites and the AGENTS.md\n * Error model table.\n *\n * The shape is a `Record<AudioErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter P3 explicit failure). Each hint embeds an executable recovery\n * action so AI users self-repair (charter P3).\n */\nexport const AUDIO_ERROR_HINTS: Readonly<Record<AudioErrorCode, string>> = {\n 'context-creation-failed':\n 'check browser supports AudioContext; verify no privacy extension blocks audio; try reloading the page after user gesture',\n 'decode-failed':\n 'ensure audio file is a valid wav/mp3/ogg/flac at the GUID path; check file integrity (truncated or empty bytes)',\n 'context-suspended':\n 'call play after a user gesture (click/tap/keydown) to trigger AudioContext.resume(); if in iframe check sandbox attribute',\n 'invalid-clip-handle':\n 'verify clip was registered via AssetRegistry.register() before spawning AudioSource; inspect active handles via assetRegistry.inspect()',\n 'bus-not-found':\n \"use 'sfx' or 'music' bus literal; custom bus names are not supported in v1 (OOS-2)\",\n};\n\n// === PhysicsErrorCode / PhysicsError / PhysicsErrorDetail -- physics error SSOT (feat-20260528-rapier-physics-2d-3d M1 / t6; extended feat-20260617-kinematic M1) ===\n//\n// Decision anchors:\n// - requirements AC-11 (PhysicsErrorCode closed union registration + AGENTS.md update)\n// - plan-strategy D-5 (PhysicsErrorCode 9 members / PhysicsError 4-field surface / PhysicsErrorDetail discriminated)\n// - charter P3 (explicit failure: exhaustive switch without default; .hint provides recovery)\n// - charter P4 (consistent abstraction: structurally parallel to AssetError / AudioError / GltfError)\n// - architecture-principles #1 SSOT (the 9 literals + class + hints table live here once;\n// engine-physics package re-exports from here)\n\n/**\n * Closed `PhysicsErrorCode` union -- 9 members (plan-strategy D-5;\n * requirements AC-11). Exhaustive `switch (err.code)` needs no default\n * fallback -- TypeScript guards union completeness at compile time\n * (charter P3 explicit failure).\n *\n * Domain-separated from `AssetErrorCode` (runtime registry, 13 members)\n * and `AudioErrorCode` (audio engine, 5 members). AI users face these 9\n * alternatives at the physics engine surface.\n *\n * | code | trigger |\n * |:--|:--|\n * | `'wasm-load-failed'` | dynamic import() of Rapier WASM rejected (network / file not found). |\n * | `'wasm-simd-unsupported'` | WebAssembly.validate returned false for SIMD test module; compat fallback also unavailable. |\n * | `'step-failed'` | Rapier World.step threw a WASM trap (invalid body parameters / NaN values). |\n * | `'invalid-body-config'` | mass <= 0 for dynamic bodies, or other validation failure. |\n * | `'body-not-found'` | entity handle resolved to no Rapier rigid body (no RigidBody spawned or handle was freed). |\n * | `'collider-not-found'` | entity handle resolved to no Rapier collider (no Collider spawned or handle was freed). |\n * | `'backend-not-registered'` | PhysicsWorld resource missing from World; use createApp(canvas, { plugins: [physicsPlugin('rapier-3d')] }) or manual registration. |\n * | `'teleport-invalid-body-type'` | teleport() called on a static or kinematic body (only dynamic allowed). |\n * | `'controller-requires-kinematic'` | moveAndSlide() called on a non-kinematic body. |\n */\nexport type PhysicsErrorCode =\n | 'wasm-load-failed'\n | 'wasm-simd-unsupported'\n | 'step-failed'\n | 'invalid-body-config'\n | 'body-not-found'\n | 'collider-not-found'\n | 'backend-not-registered'\n | 'teleport-invalid-body-type'\n | 'controller-requires-kinematic';\n\n/**\n * Per-code `PhysicsError` detail shapes -- discriminated payloads narrowed\n * by `PhysicsError.code` so AI users writing `switch (err.code)` get\n * control-flow-tightened access to the relevant detail fields (charter P3).\n */\n\n/** `wasm-load-failed` payload: carries the original error reason. */\nexport interface PhysicsWasmLoadFailedDetail {\n readonly code: 'wasm-load-failed';\n readonly reason: string;\n}\n\n/** `wasm-simd-unsupported` payload: carries the detection failure reason. */\nexport interface PhysicsWasmSimdUnsupportedDetail {\n readonly code: 'wasm-simd-unsupported';\n readonly reason: string;\n}\n\n/** `step-failed` payload: carries the WASM trap reason. */\nexport interface PhysicsStepFailedDetail {\n readonly code: 'step-failed';\n readonly reason: string;\n}\n\n/** `invalid-body-config` payload: carries the violating field + value. */\nexport interface PhysicsInvalidBodyConfigDetail {\n readonly code: 'invalid-body-config';\n readonly field: string;\n readonly value: unknown;\n}\n\n/** `body-not-found` payload: carries the entity that was not found. */\nexport interface PhysicsBodyNotFoundDetail {\n readonly code: 'body-not-found';\n readonly entity: number;\n}\n\n/** `collider-not-found` payload: carries the entity that was not found. */\nexport interface PhysicsColliderNotFoundDetail {\n readonly code: 'collider-not-found';\n readonly entity: number;\n}\n\n/** `backend-not-registered` payload: carries the attempted backend name. */\nexport interface PhysicsBackendNotRegisteredDetail {\n readonly code: 'backend-not-registered';\n readonly attemptedBackend: string;\n}\n\n/** `teleport-invalid-body-type` payload: carries the entity + disallowed body type. */\nexport interface PhysicsTeleportInvalidBodyTypeDetail {\n readonly code: 'teleport-invalid-body-type';\n readonly entity: number;\n readonly bodyType: string;\n}\n\n/** `controller-requires-kinematic` payload: carries the entity + actual body type. */\nexport interface PhysicsControllerRequiresKinematicDetail {\n readonly code: 'controller-requires-kinematic';\n readonly entity: number;\n readonly bodyType: string;\n}\n\n/**\n * Discriminated detail union for `PhysicsError`, narrowed per `PhysicsError.code`.\n * AI users obtain the concrete detail shape via `switch (err.code)` without\n * needing a fallback `as` cast (charter P3).\n */\nexport type PhysicsErrorDetail =\n | PhysicsWasmLoadFailedDetail\n | PhysicsWasmSimdUnsupportedDetail\n | PhysicsStepFailedDetail\n | PhysicsInvalidBodyConfigDetail\n | PhysicsBodyNotFoundDetail\n | PhysicsColliderNotFoundDetail\n | PhysicsBackendNotRegisteredDetail\n | PhysicsTeleportInvalidBodyTypeDetail\n | PhysicsControllerRequiresKinematicDetail;\n\n/**\n * Structured physics error -- four-field surface (`.code` / `.expected` /\n * `.hint` / `.detail`) structurally parallel to `@forgeax/engine-types`\n * `AssetError` + `AudioError` + `GltfError` (charter P4 consistent abstraction).\n *\n * AI users consume the structured triple via property access:\n * `switch (err.code) { case 'wasm-load-failed': ... err.hint ... }`\n * -- never by parsing `.message` (charter P3 explicit failure red line).\n *\n * @example AI-user exhaustive switch on the 9 members (no default fallback)\n * ```ts\n * import { PhysicsError, type PhysicsErrorCode } from '@forgeax/engine-types';\n *\n * function recover(code: PhysicsErrorCode): string {\n * switch (code) {\n * case 'wasm-load-failed': return 'check network and @dimforge/rapier3d-compat';\n * case 'wasm-simd-unsupported': return 'check browser supports WASM SIMD';\n * case 'step-failed': return 'check for NaN values in transforms';\n * case 'invalid-body-config': return 'ensure mass > 0 for dynamic bodies';\n * case 'body-not-found': return 'ensure RigidBody was spawned before use';\n * case 'collider-not-found': return 'ensure Collider was spawned before use';\n * case 'backend-not-registered': return 'use createApp(canvas, { plugins: [physicsPlugin(...)] })';\n * case 'teleport-invalid-body-type': return 'only dynamic bodies can be teleported';\n * case 'controller-requires-kinematic': return 'set RigidBody.type to kinematic';\n * }\n * }\n * ```\n */\nexport class PhysicsError extends Error {\n readonly code: PhysicsErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail?: PhysicsErrorDetail;\n\n constructor(args: {\n code: PhysicsErrorCode;\n expected: string;\n hint: string;\n detail?: PhysicsErrorDetail;\n }) {\n super(`[PhysicsError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'PhysicsError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n if (args.detail !== undefined) {\n this.detail = args.detail;\n }\n }\n}\n\n/**\n * Per-code `.hint` string literals SSOT (plan-strategy D-5 lock-in).\n * Exported so engine-physics error helpers and tests consume the same SSOT.\n *\n * The shape is a `Record<PhysicsErrorCode, string>` so future additions to\n * the closed union are a compile-time error here as well (reinforces\n * charter P3 explicit failure).\n */\nexport const PHYSICS_ERROR_HINTS: Readonly<Record<PhysicsErrorCode, string>> = {\n 'wasm-load-failed':\n 'dynamic import() of Rapier WASM rejected; check network, file path, and that @dimforge/rapier3d-compat is installed',\n 'wasm-simd-unsupported':\n 'WebAssembly.validate returned false for the SIMD test module; ensure browser supports WASM SIMD (Chrome 91+, Firefox 89+, Safari 16.4+)',\n 'step-failed':\n 'Rapier World.step threw a WASM trap; check for invalid body parameters or NaN values in transforms',\n 'invalid-body-config':\n 'check mass > 0 for dynamic bodies and valid shape parameters; see PhysicsError.detail.field',\n 'body-not-found':\n 'the entity handle did not resolve to a Rapier rigid body; ensure RigidBody was spawned before calling physics APIs',\n 'collider-not-found':\n 'the entity handle did not resolve to a Rapier collider; ensure Collider was spawned before calling physics APIs',\n 'backend-not-registered':\n \"PhysicsWorld resource not found; use createApp(canvas, { plugins: [physicsPlugin('rapier-3d')] }) or manually register a backend\",\n 'teleport-invalid-body-type':\n 'teleport is only valid for dynamic bodies; static and kinematic bodies have their position managed differently',\n 'controller-requires-kinematic':\n \"moveAndSlide requires a kinematic RigidBody; set the entity's RigidBody.type to 'kinematic'\",\n};\n\n// === RuntimeErrorCode - runtime-layer error code SSOT (feat-20260523-skin-skeleton-animation M0) ===\n//\n// Closed union of runtime-layer error code literals. Defined here as the\n// single source of truth for code-string discovery (charter F1: AI users\n// grep '@forgeax/engine-types' for all error code families). The error\n// classes that carry these codes live in @forgeax/engine-runtime.\n//\n// Decision anchors:\n// - requirements AC-29 (RuntimeErrorCode +6: skin-joint-count-exceeded /\n// skin-joint-despawned / skin-joint-path-unresolved /\n// skin-instances-coexist-forbidden / vertex-storage-buffer-unavailable /\n// skin-palette-overflow)\n// - plan-strategy D-12 (kebab-case + closed union)\n// - charter P3 (explicit failure: exhaustive switch without default)\n\n/** Closed union of runtime-layer error codes. */\nexport type RuntimeErrorCode =\n | 'shadow-invalid-config'\n | 'skin-joint-count-exceeded'\n | 'skin-joint-despawned'\n | 'skin-joint-path-unresolved'\n | 'skin-instances-coexist-forbidden'\n | 'vertex-storage-buffer-unavailable'\n | 'skin-palette-overflow'\n | 'material-resolved-empty-passes'\n | 'equirect-projection-failed'\n | 'mesh-ssbo-capacity-exceeded'\n | 'mesh-ssbo-ceiling-reached'\n | 'hdrp-caps-insufficient'\n | 'hdrp-light-budget-exceeded'\n | 'hdrp-index-list-overflow';\n\n// === GPUFlagsConstant namespace numeric aliases (5 *Flags + 8 Size/Index/Offset/SampleMask) ===\n//\n// One-to-one with the W3C CR §3.6 `unsigned long` definitions; runtime values are\n// surfaced by the global objects (GPUBufferUsage / GPUColorWrite / GPUMapMode /\n// GPUShaderStage / GPUTextureUsage).\n\n/** GPU buffer usage bit flags (OR combination of GPUBufferUsage.MAP_READ / COPY_SRC / ...). */\nexport type BufferUsageFlags = GPUBufferUsageFlags;\n\n/** GPU color write mask bit flags (GPUColorWrite.RED / GREEN / BLUE / ALPHA / ALL). */\nexport type ColorWriteFlags = GPUColorWriteFlags;\n\n/** GPU buffer map mode bit flags (GPUMapMode.READ / WRITE). */\nexport type MapModeFlags = GPUMapModeFlags;\n\n/** GPU shader stage bit flags (GPUShaderStage.VERTEX / FRAGMENT / COMPUTE). */\nexport type ShaderStageFlags = GPUShaderStageFlags;\n\n/** GPU texture usage bit flags (GPUTextureUsage.COPY_SRC / COPY_DST / TEXTURE_BINDING / ...). */\nexport type TextureUsageFlags = GPUTextureUsageFlags;\n\n/** GPU 32-bit unsigned size. */\nexport type Size32 = GPUSize32;\n\n/** GPU 64-bit unsigned size. */\nexport type Size64 = GPUSize64;\n\n/** GPU 32-bit unsigned index. */\nexport type Index32 = GPUIndex32;\n\n/** GPU 32-bit signed offset. */\nexport type SignedOffset32 = GPUSignedOffset32;\n\n/** GPU integer coordinate (used for texture / viewport extents). */\nexport type IntegerCoordinate = GPUIntegerCoordinate;\n\n/** GPU sample mask bit pattern. */\nexport type SampleMask = GPUSampleMask;\n\n/** GPU buffer dynamic offset. */\nexport type BufferDynamicOffset = GPUBufferDynamicOffset;\n\n/** GPU stencil value (reference / read mask / write mask). */\nexport type StencilValue = GPUStencilValue;\n\n// === String literal enum re-exports (already exported by @webgpu/types; we only alias) ===\n\n/** GPU texture format enum (e.g. 'rgba8unorm' / 'depth24plus' / ...). */\nexport type TextureFormat = GPUTextureFormat;\n\n/** GPU texture dimension ('1d' / '2d' / '3d'). */\nexport type TextureDimension = GPUTextureDimension;\n\n/** GPU texture view dimension ('1d' / '2d' / '2d-array' / 'cube' / 'cube-array' / '3d'). */\nexport type TextureViewDimension = GPUTextureViewDimension;\n\n/** GPU compare function ('never' / 'less' / 'equal' / 'less-equal' / 'greater' / 'not-equal' / 'greater-equal' / 'always'). */\nexport type CompareFunction = GPUCompareFunction;\n\n/** GPU filter mode ('nearest' / 'linear'). */\nexport type FilterMode = GPUFilterMode;\n\n/** GPU address mode ('clamp-to-edge' / 'repeat' / 'mirror-repeat'). */\nexport type AddressMode = GPUAddressMode;\n\n/** GPU vertex format enum ('float32' / 'float32x2' / ... 32 variants in total). */\nexport type VertexFormat = GPUVertexFormat;\n\n/** GPU vertex step mode ('vertex' / 'instance'). */\nexport type VertexStepMode = GPUVertexStepMode;\n\n/** GPU index format ('uint16' / 'uint32'). */\nexport type IndexFormat = GPUIndexFormat;\n\n/** GPU primitive topology ('point-list' / 'line-list' / 'line-strip' / 'triangle-list' / 'triangle-strip'). */\nexport type PrimitiveTopology = GPUPrimitiveTopology;\n\n/** GPU triangle cull mode ('none' / 'front' / 'back'). */\nexport type CullMode = GPUCullMode;\n\n/** GPU triangle front-face winding ('ccw' / 'cw'). */\nexport type FrontFace = GPUFrontFace;\n\n/** GPU stencil operation ('keep' / 'zero' / 'replace' / 'invert' / 'increment-clamp' / 'decrement-clamp' / 'increment-wrap' / 'decrement-wrap'). */\nexport type StencilOperation = GPUStencilOperation;\n\n/** GPU blend factor ('zero' / 'one' / 'src' / 'one-minus-src' / ...). */\nexport type BlendFactor = GPUBlendFactor;\n\n/** GPU blend operation ('add' / 'subtract' / 'reverse-subtract' / 'min' / 'max'). */\nexport type BlendOperation = GPUBlendOperation;\n\n/** GPU load op ('load' / 'clear'). */\nexport type LoadOp = GPULoadOp;\n\n/** GPU store op ('store' / 'discard'). */\nexport type StoreOp = GPUStoreOp;\n\n// === Shader pipeline trio SSOT (feat-20260508-shader-pipeline-mvp) =================\n//\n// Decision anchors:\n// - plan-strategy §S-7 + §S-9 (fully-explicit reflection + ShaderError 5-field top level)\n// - requirements §AC-04 (manifest 4 fields) + MVP-2.6 (manifest schema TS SSOT)\n// - research Finding 2 (reflection JSON field-mapping oracle, 9 boundary cases)\n// - charter proposition 4 (explicit failure) + proposition 5 (consistent abstraction:\n// dev-time and runtime errors share one shape)\n\n/**\n * Single shader manifest entry — trio + 4-field manifest SSOT (AC-04).\n *\n * | Field | Shape | Notes |\n * |:--|:--|:--|\n * | `hash` | `string` | content-addressable fingerprint (the on-disk key written by the plugin's `generateBundle`) |\n * | `wgsl` | `string` | WGSL source: relative path or inline literal (the plugin chooses; schema does not constrain) |\n * | `glsl` | `string \\| undefined` | GLSL placeholder (empty string or undefined within M1 scope; reserved for the non-WebGL fallback path) |\n * | `bindings` | `string` | `BindGroupLayoutDescriptor[]` serialized as a JSON string (output derived from reflection) |\n *\n * Written by `@forgeax/engine-shader-compiler`, persisted by `@forgeax/engine-vite-plugin-shader`,\n * loaded and consumed by `@forgeax/engine-shader` — the schema's single source of truth lives\n * in this package across all three sides (charter proposition 5: consistent abstraction).\n */\nexport interface ManifestEntry {\n readonly hash: string;\n readonly wgsl: string;\n readonly glsl: string | undefined;\n readonly bindings: string;\n}\n\n/**\n * Shader compile-time error-code closed union — 7 members\n * (feat-20260512-naga-oil-composition-hmr M3 T-09 extension; D-R7 / S-7 /\n * OQ-2 legacy 4 + D-08 new 3 for naga_oil composition).\n *\n * Symmetric in shape with `@forgeax/engine-rhi`'s `RhiErrorCode` closed union\n * (AGENTS.md error model); exhaustive `switch` needs no default fallback —\n * TypeScript guards union completeness at compile time (charter proposition 4\n * explicit failure / proposition 3 machine-readable union > prose).\n *\n * Evolution: minor-add (requirements §AC-08). The 4 legacy positions remain\n * byte-for-byte at the top (AGENTS.md `Evolution contract`: members can be\n * added only — no rename / delete / reorder). The 3 new members appear at the\n * bottom:\n * - `shader-import-not-found` — naga_oil `ImportNotFound` variant surfaces\n * when `#import <moduleId>::<symbol>` cannot bind to any module registered\n * through `options.imports` (plan-strategy D-08 + D-12 offset passthrough).\n * - `shader-circular-import` — TS-layer DFS (T-11 `detectCycle`) catches\n * `a -> b -> a` style import cycles before calling into the wasm composer\n * (plan-strategy D-03 path A + D-04 cycle first/last repetition form).\n * - `shader-define-conflict` — TS-layer pre-scan (T-12 `scanDefineConflicts`)\n * rejects the same `#define NAME` appearing in >=2 modules (plan-strategy\n * D-07; prevents naga_oil HashMap silent override from research R-07).\n *\n * | code | Trigger |\n * |:--|:--|\n * | `'shader-compile-failed'` | naga `parse_str` / `Validator` failed; also the fallback for any non-ImportNotFound naga_oil ComposerError variant (plan-strategy D-05 non-boolean #define value goes here, never a new 8th member). |\n * | `'compiler-init-failed'` | wasm load / `init()` failed (cold start / missing wasm artifact). |\n * | `'manifest-malformed'` | manifest.json schema validation failed (4 fields missing or JSON unparseable). |\n * | `'shader-not-found'` | `ShaderRegistry.get(hash)` hash miss. |\n * | `'shader-import-not-found'` | `#import <moduleId>` target absent from `options.imports` (or lacks `#define_import_path` header). `err.detail.importPath` + `err.detail.fromModuleId` narrow after the switch. |\n * | `'shader-circular-import'` | import dependency graph contains a cycle; `err.detail.cycle` carries the full chain with first/last repeated (D-04). |\n * | `'shader-define-conflict'` | same `#define NAME` declared in multiple modules; `err.detail.sites[]` lists each offending moduleId. |\n */\nexport type ShaderErrorCode =\n | 'shader-compile-failed'\n | 'compiler-init-failed'\n | 'manifest-malformed'\n | 'shader-not-found'\n | 'shader-import-not-found'\n | 'shader-circular-import'\n | 'shader-define-conflict'\n // === 5 new material-* codes (feat-20260523-shader-template-instance-split M1-T02) ===\n | 'material-schema-mismatch'\n | 'material-shader-not-found'\n | 'material-param-type-mismatch'\n | 'material-param-unknown'\n | 'material-param-missing-required'\n // === build-time superset gate (feat-20260613-material-paramschema-driven-binding M2 / w9) ===\n | 'material-shader-binding-mismatch';\n\n// === Shader error detail discriminated union (feat-20260512 M3 T-09 / D-08) =====\n//\n// Decision anchors:\n// - plan-strategy §2 D-08 (3 new typed variants keyed on `code`, structurally\n// parallel to `RhiErrorDetail` in `packages/rhi/src/errors.ts` lines\n// 165-189; 4 legacy members stay as prose detail for backwards compat).\n// - plan-strategy §2 D-04 (cycle first/last repetition form: ['a','b','a']).\n// - plan-strategy §2 D-12 (ImportNotFound offset passthrough when naga_oil\n// carries a source position on the inner variant).\n// - requirements §AC-08 (AGENTS.md §Error model ShaderErrorDetail row 3\n// variants) + §AC-15 (property access over string parsing).\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — narrow via `switch (err.detail.code)` after the\n// `switch (err.code)` tier).\n// - architecture-principles #1 SSOT (3 typed variants live here once;\n// producer site `packages/shader-compiler/src/error-mapper.ts` constructs\n// them verbatim; AGENTS.md §Error model table references this module).\n\n/**\n * Detail for the `shader-import-not-found` path (D-08 + D-12).\n *\n * `importPath` mirrors the bare `#import` target string (`'forgeax_pbr::brdf'`).\n * `fromModuleId` identifies the entry module that issued the unresolved\n * import; when the caller omitted `options.id`, this carries the\n * `<anonymous-entry-<hash8>>` placeholder (plan-strategy D-11). Optional\n * `offset` passes through the naga_oil inner-variant byte offset when present\n * (D-12); AI users surface this in error logs for IDE jump-to-source.\n */\nexport interface ShaderImportNotFoundDetail {\n readonly code: 'shader-import-not-found';\n readonly importPath: string;\n readonly fromModuleId: string;\n readonly offset?: number;\n}\n\n/**\n * Detail for the `shader-circular-import` path (D-08 + D-04).\n *\n * `cycle` lists the full import chain with the first and last element\n * repeated so consumers can visualise the loop at a glance\n * (`['a','b','c','a']`). The array is `readonly` so copy-out sites cannot\n * mutate the structure post-emit (charter proposition 4 explicit failure).\n */\nexport interface ShaderCircularImportDetail {\n readonly code: 'shader-circular-import';\n readonly cycle: readonly string[];\n}\n\n/**\n * Detail for the `shader-define-conflict` path (D-08 + D-07).\n *\n * `defineName` names the offending `#define NAME` literal; `sites` lists each\n * moduleId that declared it so the AI user can navigate to every duplicate\n * without re-scanning the source set (charter proposition 3 machine-readable\n * > prose).\n */\nexport interface ShaderDefineConflictDetail {\n readonly code: 'shader-define-conflict';\n readonly defineName: string;\n readonly sites: readonly { readonly moduleId: string }[];\n}\n\n/**\n * Detail for the `shader-compile-failed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * `compilerMessages` forwards the full 6 fields of `GPUCompilationMessage`\n * from `@webgpu/types ^0.1.69` (`message` / `type` / `lineNum` / `linePos` /\n * `offset` / `length`); the array is `readonly` so copy-out sites cannot\n * mutate the structure post-emit (charter proposition 4 explicit failure).\n * Optional `reason` carries a prose supplement when the wasm side surfaces a\n * higher-level summary alongside the raw compiler frame.\n *\n * @see RhiShaderCompileDetail in @forgeax/engine-rhi for the RhiError parallel\n * (R-7 namespace separation: `ShaderError.detail` vs `RhiError.detail` cover\n * disjoint lifecycle phases — compile-time vs async runtime dispatch — and\n * AI users distinguish them by import path).\n */\nexport interface ShaderCompileFailedDetail {\n readonly code: 'shader-compile-failed';\n readonly compilerMessages: readonly GPUCompilationMessage[];\n readonly reason?: string;\n}\n\n/**\n * Detail for the `compiler-init-failed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * Constructed by `@forgeax/engine-naga` when the wasm cold start fails\n * (`ensureReady()` rejects, the artefact is missing, or `init()` itself\n * throws). The `code` literal narrows `.detail` after the top-level\n * `switch (err.code)`; optional `reason` carries the wasm-side error message\n * when available (charter proposition 3 machine-readable union > prose).\n */\nexport interface ShaderInitFailedDetail {\n readonly code: 'compiler-init-failed';\n readonly reason?: string;\n}\n\n/**\n * Detail for the `manifest-malformed` path\n * (feat-small-20260513-dx-docs-types-cleanup D-9 / requirements §3.1.7 (A)).\n *\n * Constructed by `@forgeax/engine-naga` / `@forgeax/engine-shader-compiler`\n * when the shader manifest fails the 4-field schema (`{hash, wgsl, glsl,\n * bindings}`) or the JSON itself is unparseable. Optional `reason` carries\n * the schema validator or `JSON.parse` error message when available\n * (charter proposition 4 explicit failure: typed `.reason` access never\n * requires parsing `.message`).\n */\nexport interface ShaderManifestMalformedDetail {\n readonly code: 'manifest-malformed';\n readonly reason?: string;\n}\n\n// === 5 new material-* ShaderErrorDetail variants (feat-20260523-shader-template-instance-split M1-T02) ===\n//\n// Decision anchors:\n// - plan-strategy D-NewErrorCodes-Anchor (5 ShaderErrorCode + 5 detail variants in types SSOT)\n// - plan-strategy F-6 round 2 (material-schema-mismatch.mismatchKind is 4-element union:\n// schema-extra | shader-extra | type-mismatch | bg-overflow)\n// - requirements AC-12 (each new error code has structured detail)\n\n/**\n * Detail for `material-schema-mismatch` — paramSchema vs BGL mismatch at build-time.\n *\n * `mismatchKind` narrows on the 4-way mismatch category (F-6 round 2):\n * - 'schema-extra': paramSchema declares a name not in BGL\n * - 'shader-extra': BGL has a binding not in paramSchema\n * - 'type-mismatch': param type differs from BGL entry type\n * - 'bg-overflow': binding group count exceeds maxBindGroups (4) — AC-07\n *\n * Optional `expectedParam` / `actualBinding` carry the specific mismatch detail\n * for schema-extra / shader-extra / type-mismatch variants. `actualCount` /\n * `maxAllowed` populated for bg-overflow.\n */\nexport interface MaterialSchemaMismatchDetail {\n readonly code: 'material-schema-mismatch';\n readonly mismatchKind: 'schema-extra' | 'shader-extra' | 'type-mismatch' | 'bg-overflow';\n readonly materialShaderPath: string;\n readonly expectedParam?: string;\n readonly actualBinding?: number;\n readonly actualCount?: number;\n readonly maxAllowed?: number;\n}\n\n/**\n * Detail for `material-shader-not-found` — ShaderRegistry lookup miss.\n */\nexport interface MaterialShaderNotFoundDetail {\n readonly code: 'material-shader-not-found';\n readonly identifier: string;\n}\n\n/**\n * Detail for `material-param-type-mismatch` — a material value does not match\n * paramSchema expected type at runtime register.\n */\nexport interface MaterialParamTypeMismatchDetail {\n readonly code: 'material-param-type-mismatch';\n readonly paramName: string;\n readonly expectedType: string;\n readonly actualValue: unknown;\n}\n\n/**\n * Detail for `material-param-unknown` — material values contain a key not in\n * paramSchema.\n */\nexport interface MaterialParamUnknownDetail {\n readonly code: 'material-param-unknown';\n readonly paramName: string;\n}\n\n/**\n * Detail for `material-param-missing-required` — material values miss a key\n * that paramSchema declares without a default.\n */\nexport interface MaterialParamMissingRequiredDetail {\n readonly code: 'material-param-missing-required';\n readonly paramName: string;\n}\n\n/**\n * Detail for `material-shader-binding-mismatch` — vite-plugin-shader build-time\n * single-direction superset gate (feat-20260613-material-paramschema-driven-\n * binding M2 / D-9 / D-10).\n *\n * The actual reflected BGL must contain every binding emitted by\n * derive(schema); otherwise the build fails with this code. Extra bindings on\n * the actual side are tolerated (engine-injection placeholders such as shadow\n * / IBL / lightmap bind groups land at register-time).\n *\n * `expected` is the BGL entry derive(schema) emitted (the binding number +\n * resource layout the shader source must declare). `actual` is the entry the\n * reflector found at the same binding number, or `undefined` when the binding\n * is absent altogether. `expectedParam` names the paramSchema entry that\n * produced `expected` so AI users can grep the sidecar quickly. `mismatchKind`\n * narrows the failure category for AI-side branching.\n */\nexport interface MaterialShaderBindingMismatchDetail {\n readonly code: 'material-shader-binding-mismatch';\n readonly mismatchKind: 'binding-missing' | 'binding-type-mismatch';\n readonly materialShaderPath: string;\n readonly expected: BindGroupLayoutEntry;\n readonly actual?: BindGroupLayoutEntry;\n readonly expectedParam: string;\n}\n\n/**\n * Discriminated union of the 6 typed `.detail` variants keyed on `code`\n * (D-08 legacy 3 variants + feat-small-20260513-dx-docs-types-cleanup D-9\n * minor-add 3 variants; parallel to `RhiErrorDetail` lines 165-189 of\n * `packages/rhi/src/errors.ts`).\n *\n * AI users narrow to the per-code shape after the top-level\n * `switch (err.code)` via the nested `if (err.detail?.code === '<literal>')`\n * guard — `err.detail.compilerMessages` / `err.detail.importPath` /\n * `err.detail.reason` etc. are then typed property accesses with full IDE\n * autocomplete (charter proposition 3 machine-readable union > prose +\n * proposition 4 explicit failure).\n *\n * The 7th member `'shader-not-found'` has no typed detail variant — the naga\n * `shaderNotFound` factory leaves `.detail` undefined because the surface\n * carries no per-instance payload (the `hash` is already embedded in\n * `.message` / `.expected`; OOS-11 deferring a typed variant).\n *\n * Listed in the same order as the corresponding `ShaderErrorCode` members so\n * a reviewer can grep the two unions vertically for drift\n * (T-09 acceptance check ties `ShaderErrorDetail` grep hit to this layout).\n */\nexport type ShaderErrorDetail =\n | ShaderImportNotFoundDetail\n | ShaderCircularImportDetail\n | ShaderDefineConflictDetail\n | ShaderCompileFailedDetail\n | ShaderInitFailedDetail\n | ShaderManifestMalformedDetail\n // === 5 new material-* detail variants (feat-20260523-shader-template-instance-split M1-T02) ===\n | MaterialSchemaMismatchDetail\n | MaterialShaderNotFoundDetail\n | MaterialParamTypeMismatchDetail\n | MaterialParamUnknownDetail\n | MaterialParamMissingRequiredDetail\n // === build-time superset gate (feat-20260613-material-paramschema-driven-binding M2 / w9) ===\n | MaterialShaderBindingMismatchDetail;\n\n/**\n * Bind group layout descriptor — shape-aligned with\n * `Pick<GPUBindGroupLayoutDescriptor, 'entries' | 'label'>` (S-9 / AC-04).\n *\n * **Shape rules**:\n * - `entries` is narrowed here to a concrete `readonly BindGroupLayoutEntry[]` (the\n * spec uses `Iterable<...>`; reflection-derived output is always an array shape).\n * - All optional fields are uniformly `?: T | undefined` (guarded by\n * exactOptionalPropertyTypes).\n * - Field names match `@webgpu/types` exactly, character for character (spec-alignment rule).\n *\n * **Fully-explicit reflection JSON constraint** (plan-strategy §S-9 / D-R9):\n * the `bindings` JSON emitted by `@forgeax/engine-shader-compiler` must populate every default\n * field defined in W3C spec §5 (e.g. `hasDynamicOffset: false` / `minBindingSize: 0`);\n * `visibility` is output as the `GPUShaderStage` integer bitmask (VERTEX=0x1 /\n * FRAGMENT=0x2 / COMPUTE=0x4 OR-ed together) — string-array form is **forbidden**.\n * This type only describes the schema shape; full explicitness is enforced on the\n * producer side.\n */\nexport interface BindGroupLayoutDescriptor {\n readonly label?: string | undefined;\n readonly entries: readonly BindGroupLayoutEntry[];\n}\n\n/**\n * Single bind group layout entry — shape-aligned with\n * `@webgpu/types.GPUBindGroupLayoutEntry`.\n *\n * `binding` / `visibility` are required; the four resource layouts (buffer / sampler /\n * texture / storageTexture) form the \"exactly one set\" constraint per W3C spec §5\n * (`externalTexture` is out of scope for the forgeax MVP and is not surfaced here yet).\n */\nexport interface BindGroupLayoutEntry {\n readonly binding: GPUIndex32;\n readonly visibility: GPUShaderStageFlags;\n readonly buffer?: GPUBufferBindingLayout | undefined;\n readonly sampler?: GPUSamplerBindingLayout | undefined;\n readonly texture?: GPUTextureBindingLayout | undefined;\n readonly storageTexture?: GPUStorageTextureBindingLayout | undefined;\n}\n\n// === RemoteHandle (feat-20260629-inspector-two-layer-model M4 / w17) ========\n//\n// Decision anchors:\n// - plan-strategy secondary D-6: RemoteHandle defined in @forgeax/engine-types\n// (neutral package, no temporal coupling to @forgeax/engine-remote)\n// - requirements AC-11: app.remote typed as RemoteHandle | undefined,\n// exposed on the createApp return value for host inspection\n//\n// Shape:\n// port — number, the listen port (determined by the server on startup)\n// close — Promise<void>, tear down the server (Surface Plugin pattern\n// from startServer's returned ConsoleHandle)\n\n/**\n * Handle for a running remote eval server (feat-20260629-inspector-two-layer-model M4).\n *\n * AI users access `app.remote.port` for WS connection / status, and call\n * `await app.remote.close()` to tear down. The field is `undefined` when the\n * server is not started (production build or headless without opt-in).\n *\n * @see {@link startServer} in @forgeax/engine-remote for the producer side\n */\nexport interface RemoteHandle {\n /** Server listen port (number). Non-zero when the server is running. */\n readonly port: number;\n /** Tear down the server. Returns a Promise that resolves once the WS\n * server has closed all connections. */\n close(): Promise<void>;\n}\n\n// === Remote error model SSOT (feat-20260629-inspector-two-layer-model) ====\n//\n// Decision anchors:\n// - requirements §10.1 + §10.2 + AC-05 (`RemoteErrorCode` 5-member\n// closed union + structured `RemoteError` shape independent from RhiError /\n// ShaderError)\n// - plan-strategy §2 D-5 (rename InspectorErrorCode -> RemoteErrorCode,\n// delete inspector-write-denied, delete script-timeout, rename\n// console-* -> server-*)\n// - charter proposition 3 (machine-readable union > prose) +\n// proposition 4 (explicit failure — `switch (err.code)` is exhaustive\n// without default fallback) + proposition 5 (consistent abstraction —\n// structurally aligned with @forgeax/engine-rhi's RhiError surface)\n// - architecture-principles #1 SSOT (the 5 string literals + structured\n// structural shape live here once; @forgeax/engine-remote's runtime `RemoteError`\n// class implements this interface; consumers import the type\n// alias without dragging the runtime class through static deps —\n// parallel to the existing ShaderErrorCode pattern)\n\n/**\n * Closed `RemoteErrorCode` union — 5 members (feat-20260629-inspector-two-layer-model\n * D-5; requirements AC-05). Exhaustive `switch` needs no default\n * fallback — TypeScript guards union completeness at compile time\n * (charter proposition 4 explicit failure + proposition 3 machine-readable\n * union > prose).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'script-syntax-error'` | Script body is not parseable JavaScript (SyntaxError from eval). |\n * | `'script-runtime-error'` | Script threw a non-syntax exception during execution (e.g. ReferenceError / TypeError). |\n * | `'server-startup-failed'` | The remote eval server failed to come up: WebSocketServer raised 'error' (EADDRINUSE / other listen failure), dynamic-import resolution failed, or the target package lacks the `startServer` factory. |\n * | `'server-not-running'` | CLI client's `new WebSocket('ws://localhost:<port>/inspector')` failed to connect (server not started; `app.remote` not wired in the demo). |\n * | `'eval-result-not-serializable'` | A successful eval result cannot cross the JSON-RPC wire, such as a BigInt or cyclic object. |\n *\n * **Independence from `RhiError | ShaderError` union** — `RemoteErrorCode`\n * is **not** merged into the GPU / asset error union (charter proposition 5 +\n * architecture-principles #1 SSOT). Engine-side errors stream is OOS-1\n * (errors.subscribe v2 spinoff); remote callers only face these 5\n * alternatives.\n */\nexport type RemoteErrorCode =\n | 'script-syntax-error'\n | 'script-runtime-error'\n | 'server-startup-failed'\n | 'server-not-running'\n | 'eval-result-not-serializable';\n\n/**\n * Structural shape of a forgeax remote error (feat-20260629-inspector-two-layer-model\n * D-5). Structured surface mirroring `@forgeax/engine-rhi` `RhiError`\n * (charter proposition 5 consistent abstraction; AGENTS.md \"Errors are\n * structured\"):\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail).\n * - `.hint` actionable recovery guidance (L2 detail).\n * - `.message` auto-composed string for human stack traces (AI users\n * prefer property access on `.code` / `.expected` / `.hint`).\n * - `.name` Error name marker (`'RemoteError'`) for cross-realm\n * dispatch under JSON-RPC transport.\n *\n * This interface intentionally extends `Error` so a runtime `RemoteError`\n * **class** (defined in `@forgeax/engine-remote/errors`) satisfies the contract\n * without re-declaring the inherited `name` / `message` slots.\n *\n * AI users consume the structured triple via property access — never by\n * parsing `.message` (charter proposition 4 explicit failure red line).\n */\nexport interface RemoteError extends Error {\n readonly code: RemoteErrorCode;\n readonly expected: string;\n readonly hint: string;\n /**\n * Optional discriminated detail payload (feat-20260517 D-7). Per-code\n * variant carries structured provenance that would otherwise pollute the\n * single-line `.hint` copy. AI users narrow via `switch (err.code)`; the\n * `.detail` slot is `undefined` for codes whose discriminator has no\n * payload (charter P4 explicit failure: signal absence by type).\n */\n readonly detail?: RemoteErrorDetail;\n}\n\n/**\n * Discriminated detail union for {@link RemoteError} (feat-20260517 D-7).\n * Each variant pairs a {@link RemoteErrorCode} member with the\n * structured payload AI users need to act on the error without grepping\n * prose. Variants without payload are intentionally absent — the\n * `RemoteError.detail` slot is `undefined` for those codes.\n *\n * The `server-startup-failed` variant carries bounded startup provenance.\n */\nexport type RemoteErrorDetail = ServerStartupFailedDetail | EvalResultNotSerializableDetail;\n\n/**\n * `server-startup-failed` discriminator variant with bounded provenance.\n */\nexport interface ServerStartupFailedDetail {\n readonly code: 'server-startup-failed';\n readonly removedAt: string;\n readonly docAnchor: string;\n}\n\n/**\n * `eval-result-not-serializable` discriminator variant. The shape is a\n * bounded classification only; it carries no part of the returned object\n * so failed transport cannot leak arbitrary engine state.\n */\nexport interface EvalResultNotSerializableDetail {\n readonly code: 'eval-result-not-serializable';\n readonly shape: 'bigint' | 'cyclic-object' | 'unsupported';\n}\n\n// === Metric registry error model SSOT (feat-20260512-threejs-pixel-parity-bench) ===\n//\n// Decision anchors:\n// - requirements §3.5 + AC-04 + AC-05 + AC-11 (`MetricErrorCode` 4-member closed\n// union elevated to TS alias; B-1 regression-prevention callout — exhaustive\n// `switch (err.code)` without `default:` must compile under tsc strict)\n// - plan-strategy §2 D-P3 (MetricErrorCode TS alias goes first in the topology;\n// M1 T-001 ships only the 4 legacy members verbatim from AGENTS.md Error\n// model table)\n// - research Finding 9 (`MetricErrorCode` currently has zero TS alias = direct\n// B-1 regression risk; §6 g9 checklist item 1: introduce\n// `export type MetricErrorCode = ...` in `packages/types/src/index.ts`,\n// structurally parallel to ShaderErrorCode / RemoteErrorCode)\n// - charter proposition 3 (machine-readable union > prose) + proposition 4\n// (explicit failure — closed-union exhaustive switch needs no default fallback;\n// tsc strict mode guards completeness) + proposition 5 (consistent abstraction —\n// structurally aligned with @forgeax/engine-rhi RhiError and RemoteError)\n// - architecture-principles #1 SSOT (the 4 string literals live here once;\n// `scripts/check-metrics-declared.mjs` / `scripts/metrics/run-all.mjs` /\n// `scripts/metrics/run-fps.mjs` are .mjs producer sites that emit the same\n// literals at throw points; parallel to ShaderErrorCode pattern)\n\n/**\n * Closed `MetricErrorCode` union — 4 members (M1 T-001 elevation of the\n * pre-existing 4 `.mjs` producer literals to a TS alias; research Finding 9\n * §6 g9 checklist item 1). Exhaustive `switch` needs no default fallback —\n * TypeScript guards union completeness at compile time (charter proposition 4\n * explicit failure + proposition 3 machine-readable union > prose).\n *\n * | code | trigger |\n * |:--|:--|\n * | `'metric-not-declared'` | a workspace member lacks `package.json#forgeax.metrics` or the declaration is not a plain object; emitted by `scripts/check-metrics-declared.mjs` + `scripts/metrics/run-all.mjs`. |\n * | `'metric-kind-unknown'` | `forgeax.metrics` contains a key not in the closed `MetricKind` union (`bundle-size` / `fps` / `bench` / `gate` / `spike-report`); typo guard via ajv `additionalProperties: false`. |\n * | `'metric-status-not-ok'` | dispatcher (bundle-size / bench / gate / fps / spike-report) returned `status !== 'ok'`; the offending `report/<package>/<kind>.json` carries the value-vs-threshold detail. |\n * | `'metric-schema-malformed'` | `forgeax-metrics.schema.json` failed to parse / compile as JSON Schema 2020-12; precondition failure surfaced by both `check-metrics-declared.mjs` and `run-all.mjs`. |\n *\n * **B-1 regression prevention** (requirements AC-05 + AC-11): an alias without\n * a TS consumer site cannot be exhaustively switched; M1 T-002 adds type-level\n * tests against this alias, and M2 evaluator + M2 runner CLI add the two\n * non-test exhaustive `switch (err.code)` consumer sites (D-P9 plan-strategy\n * decision).\n *\n * Per-feat extension to 6 members (M1 T-002, D-P3): `'pixel-parity-threshold-exceeded'`\n * + `'pixel-parity-capture-failed'` extend the alias at the bottom; AGENTS.md\n * Error model table flips from `(4)` to `(6)` in lockstep. The two new members\n * encode the double-gate of the pixel-parity bench (research Finding 10 +\n * plan-strategy D-P2): Layer A per-pixel YIQ tolerance ` perPixelThreshold` is\n * pixelmatch-internal and never raises on its own; Layer B aggregate cap\n * `threshold` raises `'pixel-parity-threshold-exceeded'`; any capture-side\n * failure (chromium launch / vite preview / readPixels / size mismatch /\n * pixelmatch internal throw) collapses into `'pixel-parity-capture-failed'`\n * with a `.detail.stage` discriminator (charter proposition 5 consistent\n * abstraction — pixelmatch internal exception does NOT get a third member;\n * see D-P3 decision rationale).\n */\nexport type MetricErrorCode =\n | 'metric-not-declared'\n | 'metric-kind-unknown'\n | 'metric-status-not-ok'\n | 'metric-schema-malformed'\n | 'pixel-parity-threshold-exceeded'\n | 'pixel-parity-capture-failed';\n\n/**\n * Per-code detail shape for the four legacy `MetricErrorCode` members\n * (`'metric-not-declared'` / `'metric-kind-unknown'` / `'metric-status-not-ok'`\n * / `'metric-schema-malformed'`).\n *\n * The four legacy `.mjs` producer sites (`scripts/check-metrics-declared.mjs`,\n * `scripts/metrics/run-all.mjs`, `scripts/metrics/run-fps.mjs`) emit textual\n * `[reason] / [hint]` lines and never carry a structured payload — they live\n * in CI-only scripts and exit 1 directly. The `.detail` slot is therefore left\n * `undefined` so AI consumers do not waste a narrowing step looking for a\n * non-existent payload (charter proposition 4 explicit failure: signal absence\n * by type).\n */\nexport interface MetricLegacyDetail {\n readonly stage?: undefined;\n}\n\n/**\n * Detail shape exclusive to the `'pixel-parity-threshold-exceeded'` path\n * (M1 T-002 / D-P11). Carries the full numeric verdict so AI users can\n * surface the value-vs-threshold delta in stderr / sticky-comment renderings\n * without parsing `.message`.\n *\n * | Field | Meaning |\n * |:--|:--|\n * | `diffPixelCount` | Aggregate count from `pixelmatch(left, right, ...)` (Layer B reading). |\n * | `diffPercent` | `diffPixelCount / (width * height)` rendered as a 0..1 float for sticky-comment formatting. |\n * | `maxChannelDelta` | Maximum per-channel uint8 delta across all differing pixels (0..255). Helps disambiguate \"many tiny diffs\" from \"few big diffs\". |\n * | `threshold` | The declared Layer B integer cap (`package.json#forgeax.metrics.bench.pixelDiff.threshold`). |\n * | `perPixelThreshold` | The Layer A `pixelmatch` per-pixel YIQ float threshold actually used; equals the declared value or the `0.1` fallback (D-P2 default semantics). |\n *\n * The exhaustive discriminator is `code === 'pixel-parity-threshold-exceeded'`\n * — AI users access `.detail.diffPixelCount` directly after the type guard\n * with full IDE autocomplete (charter proposition 3 machine-readable union >\n * prose; AI-user review F-1 IDE autocomplete affordance).\n */\nexport interface ParityThresholdDetail {\n readonly diffPixelCount: number;\n readonly diffPercent: number;\n readonly maxChannelDelta: number;\n readonly threshold: number;\n readonly perPixelThreshold: number;\n}\n\n/**\n * Detail shape exclusive to the `'pixel-parity-capture-failed'` path (M1 T-002\n * / D-P11). Carries a discriminator `.stage` that pinpoints which step of the\n * capture pipeline collapsed (charter proposition 5 consistent abstraction:\n * pixelmatch-internal throw becomes `.stage='diff'` rather than a third\n * `MetricErrorCode` member — plan-strategy D-P3 decision).\n *\n * | `.stage` | trigger |\n * |:--|:--|\n * | `'chromium-launch'` | `chromium.launch({...})` threw (research Finding 6: `--enable-unsafe-webgpu` flag still rejected on the host). |\n * | `'vite-preview'` | spawned vite preview never reached `wait-on tcp 30s` (research Finding 4 cleanup pattern). |\n * | `'pixel-readback'` | `gl.readPixels(...)` or `commandEncoder.copyTextureToBuffer(...)` failed, or `window.__captureLeft/Right` was missing. |\n * | `'size-mismatch'` | left and right `Uint8Array.length` differ; `leftSize` / `rightSize` carry the actual byte counts. |\n * | `'diff'` | `pixelmatch(left, right, ...)` itself threw (charter proposition 4 explicit failure: no silent catch; EC-06). |\n *\n * Optional `leftSize` / `rightSize` are populated for the `'size-mismatch'`\n * stage; they are absent for the other stages because the failure happened\n * before any byte count was known.\n */\nexport interface ParityCaptureDetail {\n readonly stage: 'chromium-launch' | 'vite-preview' | 'pixel-readback' | 'size-mismatch' | 'diff';\n readonly leftSize?: number;\n readonly rightSize?: number;\n /**\n * Optional human-readable cause string for the failure (typically the\n * caught `Error.message` text or an inferred reason). Aligned with ECMA\n * 2022 `Error.cause` naming convention so IDE hover invokes the same mental\n * model. Filled by `scripts/bench/pixel-parity.mjs` at every stage that\n * surfaces a non-empty message; absent when the failure is purely\n * structural (e.g. `'size-mismatch'` where `leftSize` / `rightSize` carry\n * the diagnostic payload instead).\n */\n readonly cause?: string;\n}\n\n/**\n * Non-optional detail projection carried by `MetricError`.\n *\n * `MetricError` owns the complete code-to-detail relation. `NonNullable` removes\n * only the four legacy absence markers; parity payloads and the legacy detail\n * shape remain in the public family without a second manually maintained list.\n */\nexport type MetricErrorDetail = NonNullable<MetricError['detail']>;\n\n/**\n * Structural shape of a forgeax metric error (feat-20260512 T-002).\n *\n * Three-field surface (`.code` / `.expected` / `.hint`) plus per-code-narrowed\n * `.detail`, structurally aligned with `@forgeax/engine-rhi` `RhiError` and\n * `InspectorError` (charter proposition 5 consistent abstraction; AGENTS.md\n * \"Errors are structured. Return Result, never throw for expected failures\").\n *\n * `MetricError` is a TypeScript discriminated union of 6 per-code interfaces;\n * each variant narrows `.detail` to the corresponding `MetricErrorDetail`\n * branch. AI users perform a single `switch (err.code)` and pick up\n * `.detail.diffPixelCount` (threshold-exceeded path) or `.detail.stage`\n * (capture-failed path) with full IDE autocomplete (AI-user review F-1\n * affordance; D-P11).\n *\n * - `.code` closed union member (L1 key signal; switch-able).\n * - `.expected` expected-state description (L2 detail; mirrors the `[reason]`\n * line emitted by `failStructured(...)` in the three `.mjs`\n * producer sites).\n * - `.hint` actionable recovery guidance (L2 detail; mirrors the `[hint]`\n * line in `failStructured(...)`).\n * - `.detail` path-specific structured payload narrowed per `.code`.\n *\n * AI users consume the structured triple via property access — never by\n * parsing `.message` (charter proposition 4 explicit failure red line).\n */\nexport type MetricError =\n | (MetricErrorBase & {\n readonly code: 'metric-not-declared';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-kind-unknown';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-status-not-ok';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'metric-schema-malformed';\n readonly detail?: MetricLegacyDetail | undefined;\n })\n | (MetricErrorBase & {\n readonly code: 'pixel-parity-threshold-exceeded';\n readonly detail: ParityThresholdDetail;\n })\n | (MetricErrorBase & {\n readonly code: 'pixel-parity-capture-failed';\n readonly detail: ParityCaptureDetail;\n });\n\n/**\n * Common base of every `MetricError` variant (D-P11 internal helper —\n * never instantiated on its own, only intersected into the per-code\n * branches of `MetricError`).\n */\ninterface MetricErrorBase {\n readonly code: MetricErrorCode;\n readonly expected: string;\n readonly hint: string;\n}\n\n// === Pack-index catalog entry POD (feat-20260517-vite-plugin-image-build-time-cook D-2) ===\n//\n// PackIndexEntry is the in-memory shape of one row in `pack-index.json` (build\n// path) and `/__pack/index` JSON response (dev path). It is the SSOT contract\n// between the build-time catalog builder (`@forgeax/engine-vite-plugin-pack`)\n// and the runtime asset loader (`@forgeax/engine-runtime` `parseAssetPayload`).\n//\n// Decision anchors:\n// - plan-strategy D-2 (5-field metadata sub-structure: width / height /\n// format / colorSpace / mipmap, mirrors TextureAsset POD field names so\n// `metadata.colorSpace` greps to the same surface across catalog / POD /\n// sidecar).\n// - plan-strategy D-5 (sidecar `mipmap: 'auto' | 'none'` is mapped to the\n// `boolean` form by the catalog builder; runtime is unaware of the\n// string token).\n// - charter P1 (progressive disclosure -- core 4 fields stay flat,\n// image-only metadata sinks into a sub-structure that texture-arm\n// consumers narrow into).\n// - charter P4 (consistent abstraction -- `metadata` field-by-field\n// mirrors `TextureAsset` POD field names; `width` / `height` / `format`\n// / `colorSpace` / `mipmap` align byte-for-byte).\n//\n// Backward compatibility (D-2 'minor' evolution):\n// - `metadata` is `?: ImageMetadata | undefined` -- legacy 4-field entries\n// emitted by older builds (or future non-texture kinds: 'mesh' / 'scene' /\n// 'material') stay valid; runtime consumers narrow on `entry.metadata !==\n// undefined` before accessing fields.\n// - The interface stays open over `kind` (string) so future 'audio' /\n// 'video' arms can join without re-typing PackIndexEntry; the texture\n// arm narrows via `entry.kind === 'texture'` + `entry.metadata`\n// existence in `parseAssetPayload`.\n\n/**\n * Metadata sub-structure carried by `PackIndexEntry` rows of `kind: 'texture'`.\n *\n * Five fields mirror `TextureAsset` POD field names (`width` / `height` /\n * `format` / `colorSpace` / `mipmap`) so AI users can grep one identifier and\n * see the same surface in catalog rows, sidecar `*.meta.json`\n * `importSettings`, and the runtime `TextureAsset` POD (charter P4 consistent\n * abstraction).\n *\n * `width` / `height` are optional because dev-mode catalog rows folded from a\n * `*.meta.json` sidecar may lack pixel dimensions until `parseImage`\n * decodes the JPG bytes; build-mode (import) rows always have them filled\n * because the import step has already run `parseImage` to produce the RGBA\n * bytes.\n *\n * `format` is `GPUTextureFormat` to align with the `TextureAsset.format`\n * field (math-free, spec-aligned with `@webgpu/types ^0.1.70`).\n *\n * `colorSpace` and `mipmap` are required because the sidecar\n * `importSettings` always carries them (D-5: `'auto'` / `'none'` string\n * tokens are mapped to `true` / `false` at the catalog builder; runtime never\n * sees the string form).\n *\n * `compression` is the build-time compression level this image artefact\n * was stored with. Loop 1 supports `'none'` (passthrough) and `'zstd'`.\n * Loop 2 may add members like `'basis-uastc'`. Absent for legacy rows.\n */\n\n/**\n * Asset compression strategy — closed literal union (SSOT, D-3 / D-9).\n *\n * Five flat, mutually-exclusive members describing how an artefact is stored:\n * - `'none'` — pass-through (uncompressed bytes)\n * - `'zstd'` — generic zstd container compression (Loop 1)\n * - `'basis-etc1s'` / `'basis-uastc'` / `'basis-uastc-hdr'` — a Basis-encoded\n * KTX2 texture (Loop 2). The `basis-*` members fully describe the delivered\n * encoding: the KTX2 container carries its own supercompression (self-\n * described by the KTX2 header) and does NOT stack an outer `'zstd'` layer\n * (mutual exclusion by construction, D-3). The `basis-` kebab prefix is\n * visually distinct from GPU texture-format literals (naming rule, §8).\n *\n * Add-only-minor: Loop 2 appends the three `basis-*` members without repainting\n * `'none'` / `'zstd'` semantics (AC-11a). A missing / `undefined` field means a\n * legacy uncompressed artefact (E1 backward-compat).\n */\nexport type AssetCompression = 'none' | 'zstd' | 'basis-etc1s' | 'basis-uastc' | 'basis-uastc-hdr';\n\nexport interface ImageMetadata {\n readonly kind: 'texture';\n readonly width?: number;\n readonly height?: number;\n readonly format: GPUTextureFormat;\n readonly colorSpace: 'srgb' | 'linear';\n readonly mipmap: boolean;\n /** Build-time compression strategy used for this image artefact. `undefined` for legacy assets. */\n readonly compression?: AssetCompression;\n /**\n * Sidecar control-plane request for the offline texture encoder (D-12).\n * `'auto'` derives the delivery encoding from `colorSpace` + HDR source;\n * `'etc1s'` / `'uastc'` force a Basis encoding; `'none'` keeps the\n * uncompressed `.bin` path. Aligns with the mipmap sidecar tri-state idiom.\n * The `'auto'` default semantics activate in M5; M3 keeps the default `'none'`.\n */\n readonly compressionMode?: 'auto' | 'etc1s' | 'uastc' | 'none';\n /** Optional asset-owned cooked-payload target dimension. */\n readonly downscaleMaxDimension?: number;\n}\n\nexport type {\n AssetAuthoringCapability,\n AssetAuthoringUnavailableReason,\n AssetBindingCapability,\n AssetBindingTarget,\n AssetPlacementCapability,\n AssetRelation,\n AssetRelationPolicy,\n AssetRelationType,\n AssetSubjectRef,\n AssetSubjectType,\n CatalogDiagnostic,\n CatalogDiagnosticSeverity,\n CatalogLifecycle,\n CatalogOperationDescriptor,\n CatalogOperationName,\n CatalogOperations,\n CatalogProjection,\n CatalogProjectionInput,\n CatalogSubject,\n CookExecution,\n ExistingOutput,\n ImportedOutputDeclaration,\n KindChange,\n MatchConflict,\n ProducerContractDiagnostic,\n ProducerContractErrorCode,\n ProducerContractResult,\n ProposedOutput,\n ProviderProvenance,\n ResourceRevision,\n ScenePublicationFence,\n SourceOverrideDescriptor,\n SourceOverrideDiagnostic,\n SourceOverrideErrorCode,\n SourceOverrideMap,\n SourceOverridePayload,\n SourceOverrideValidationResult,\n TopologyConflictReason,\n TopologyDiff,\n TopologyPreserved,\n UiAuthoringCapability,\n UiAuthoringProjection,\n} from './asset-producer';\nexport {\n authoringCapabilityForAssetKind,\n canonicalizeSourceOverrides,\n catalogOperationsFor,\n isCatalogProjectionValid,\n MESH_MATERIAL_SLOT_SOURCE_OVERRIDE_PAYLOAD_SCHEMA,\n validateSourceOverrideMap,\n} from './asset-producer';\n/**\n * One row in the pack-index catalog (`pack-index.json` for build path,\n * `/__pack/index` JSON response for dev path).\n *\n * Core fields (4) stay flat for AI users to grep one identifier:\n * - `guid`: UUIDv5/v7 lowercase string (asset identity SSOT)\n * - `packageUrl`: cooked Pack v2 package navigation URL.\n * - `kind`: closed-string discriminator (`'texture'` / `'mesh'` / `'scene'`\n * / `'material'` / future arms); narrowed by runtime `parseAssetPayload`\n * via exhaustive switch.\n * - `sourcePath`: relative path to the on-disk source artefact for\n * debugging + grep (dev: source JPG path; build: same source JPG path\n * even though `packageUrl` points to the cooked package).\n *\n * Optional 5th field:\n * - `metadata`: `ImageMetadata | undefined` -- present when `kind ===\n * 'texture'`; absent for non-texture kinds (legacy `.pack.json` entries\n * emit 4-field rows). Runtime consumers narrow with `entry.metadata !==\n * undefined` before consumption (D-2 backward-compat strategy).\n */\nexport type {\n AssetPublicationEnvelope,\n AssetPublicationEvidenceUsage,\n AssetPublicationExternalEvidence,\n AssetPublicationFailure,\n AssetPublicationFailureStage,\n AssetPublicationLocator,\n AssetPublicationOutput,\n AssetPublicationReceipt,\n AssetPublicationRecovery,\n CatalogDelta,\n CatalogDeltaValidationError,\n CatalogEntry,\n CatalogEntry as PackIndexEntry,\n CatalogEntryV2,\n CatalogRevisionPoint,\n CatalogRevisionWindow,\n} from './catalog';\nexport { catalogDeltaDigest, catalogEntryDigest, validateCatalogDelta } from './catalog';\n\n// === InspectEntry / InspectSnapshot (feat-20260618-asset-and-pack-name-fields M1 / w3) ===\n//\n// Decision anchors:\n// - plan-strategy D-9 (InspectEntry.name: string via resolveName, non-optional\n// with empty string as legal value; relocated from runtime private to types\n// for single-entry discoverability per charter F1)\n// - requirements AC-12 (inspector assets root carries resolved name per entry)\n//\n// These types were originally private interfaces in asset-registry.ts.\n// They are promoted to @forgeax/engine-types so console + future inspector\n// consumers import them from a single entry point (charter F1).\n\n/** One row in the inspector's `assets[]` snapshot (JSON-RPC over WS). */\nexport interface InspectEntry {\n readonly guid: string;\n /** Asset kind discriminant string (e.g. `'mesh'`, `'texture'`, `'scene'`). */\n readonly kind: string;\n /** Display name resolved by resolveName (empty string is legal). */\n readonly name: string;\n}\n\n/** Snapshot returned by `AssetRegistry.inspect()` -- the inspector root. */\nexport interface InspectSnapshot {\n readonly assets: ReadonlyArray<InspectEntry>;\n}\n\n// === Loader contract SSOT (feat-20260603-asset-import-loader-injection M1 / w3) ===\n//\n// Decision anchors:\n// - plan-strategy D-1 (runtime LoaderRegistry dispatches on `asset.kind`;\n// host injects loaders via `wireDefaultLoaders`, mirroring Console\n// `wireDefaultInspectors`) + D-2 (contract SSOT lives here in\n// `@forgeax/engine-types`, math-free, so `@forgeax/engine-runtime` only\n// depends on the interface, never reverse-imports a concrete loader)\n// - requirements core principle (third DIP instance after RHI / Console)\n// - charter P3 (structured failure) + P4 (consistent abstraction)\n//\n// A `Loader` is the runtime-side half of the import/load split: it turns an\n// already-imported internal artefact (a `.pack.json` payload, or fetched\n// bytes for texture / font) into an in-memory `Asset` POD. It stays pure of\n// the registry's bookkeeping — publication is the AssetRegistry's job,\n// never the loader's (plan-strategy D-2).\n//\n// Two dispatch shapes share this one contract (the asymmetry is intentional,\n// matching the two pre-existing AssetRegistry load paths the M1 refactor\n// converges; research Finding 1 + Finding 2):\n// (a) inline pack-payload kinds (mesh / scene / material /\n// skeleton / skin / animation-clip) parse synchronously and return\n// `Asset | undefined` (`undefined` = parse rejected, the caller maps it\n// to a structured `AssetError`).\n// (b) upstream-branch kinds (texture / font / equirect) fetch + decode\n// asynchronously and return a `Promise<LoaderAsyncResult>` carrying either\n// the produced `Asset` POD or a structured error.\n\n/**\n * Result envelope returned by the async branch of {@link Loader.load}\n * (texture / font). Mirrors the `Result<T, E>` shape used across the engine\n * (`.ok` discriminant) but is declared math-free here so\n * `@forgeax/engine-types` need not import `@forgeax/engine-rhi`. The error is\n * left as `unknown` so the runtime can surface its own\n * `AssetError | ImageError | RhiError` union without leaking those classes\n * into the types package (charter P4 — the runtime narrows; types stays\n * dependency-free).\n */\nexport type LoaderAsyncResult<P = Asset> =\n | { readonly ok: true; readonly value: P }\n | { readonly ok: false; readonly error: unknown };\n\n/**\n * Output of {@link Loader.load}. The synchronous arm returns `Asset` (parse\n * succeeded) or `undefined` (parse rejected); the asynchronous arm returns a\n * `Promise<LoaderAsyncResult>`.\n */\nexport type LoaderOutput<P = Asset> =\n | P\n | undefined\n | { readonly ok: false; readonly error: ParseErrorDetail }\n | Promise<LoaderAsyncResult<P>>;\n\n/**\n * Capabilities the host wires into a {@link Loader} at load time. A loader\n * receives this context so it never reaches back into AssetRegistry\n * internals (pipeline isolation, architecture-principles #4).\n *\n * Exactly three capabilities (plan-strategy D-3 rationale):\n * - `fetchBinary(url)` — fetch raw bytes for the artefact (texture import\n * `.bin`, `.hdr`, source image, font pack JSON).\n * - `resolveRef(guid)` — recursively resolve a referenced sub-asset GUID to\n * its registered handle id (font atlas / sampler). Returns the raw handle\n * number so the loader can stamp it into the produced POD; the runtime\n * performs the recursive typed load + registration underneath.\n * - `device` — opaque GPU device slot, present for future GPU-touching\n * loaders; current loaders register CPU PODs only and never touch it\n * (research Finding 3 — texture GPU upload is decoupled from load time via\n * the pull-model `GpuResourceStore`). Typed `unknown` so types stays\n * RHI-free.\n *\n * F21 (feat-20260621): the error-contextualization callback has been removed.\n */\nexport interface ParseErrorDetail {\n readonly localId: number;\n readonly component: string;\n readonly field: string;\n readonly index: number;\n readonly refsLength: number;\n}\n\n/**\n * Device texture-compression capabilities the transcode target selector reads\n * (feat-20260707 M5 / D-8, D-11).\n *\n * Three independent booleans mirror the WebGPU `texture-compression-{bc,etc2,\n * astc}` device features (and the `RhiCaps.textureCompression{Bc,Etc2,Astc}`\n * triple they are projected from — createRenderer does the one-line RhiCaps ->\n * TranscodeCaps projection). This shape is structurally identical to the codec\n * package's own `TranscodeCaps` (`@forgeax/engine-codec`): the codec keeps a\n * LOCAL copy on purpose (D-8 — codec is a pure, dependency-light transcode\n * library and must not take a `@forgeax/engine-types` edge just to name its\n * pure-function input). The runtime passes a value of this type straight into\n * `selectTranscodeTarget` by structural compatibility; there is exactly one\n * value threaded through `LoadContext`, so no fact is duplicated at runtime.\n */\nexport interface TranscodeCaps {\n readonly bc: boolean;\n readonly etc2: boolean;\n readonly astc: boolean;\n}\n\nexport interface LoadContext {\n /**\n * Fetch raw bytes for an asset artefact, with optional decompression.\n *\n * feat-20260706 M3 / w19: extended signature per D-2 — a `compression`\n * opt triggers the decompression gate inside the closure\n * (`@forgeax/engine-codec` lazy-init). `undefined` / `'none'` = E1\n * pass-through (backward-compat for legacy catalog rows).\n */\n fetchBinary(\n url: string,\n opts?: { readonly compression?: AssetCompression },\n ): Promise<\n | { readonly ok: true; readonly value: Uint8Array }\n | { readonly ok: false; readonly error: unknown }\n >;\n resolveRef(\n guid: string,\n ): Promise<\n { readonly ok: true; readonly value: number } | { readonly ok: false; readonly error: unknown }\n >;\n /**\n * feat-20260613-material-paramschema-driven-binding M4 / w22 (D-5 graceful):\n * derive(paramSchema).textureFieldNames for the given material-shader id,\n * built from the registered shader's paramSchema. Used by materialLoader\n * to know which material value fields carry refs[] indices vs scalar values\n * (replacing the deleted hardcoded texture-field allowlist Set per AC-03).\n *\n * Returns `undefined` when the shader is not yet registered (the cross-\n * worktree shader-late-register path of plan R-4): the loader then falls\n * back to a graceful \"try every int paramValue as a refs index\" walk that\n * may misclassify scalar f32 fields whose value happens to land in\n * [0, refs.length); the extract layer (M4 / w23) catches mis-typed\n * handles via paramSchema validation and falls back to MISSING_TEXTURE_HANDLE.\n */\n getMaterialShaderTextureFieldNames?(shaderId: string): ReadonlySet<string> | undefined;\n /**\n * feat-20260707 M5 / w33 (D-11): device compression caps the texture / equirect\n * Basis arms feed to `selectTranscodeTarget` to pick a transcode target. Wired\n * by `createRenderer` from `RhiCaps` (D-8 one-line projection); a bare\n * AssetRegistry (test / headless path) defaults to all-false, which drives the\n * uncompressed `rgba8unorm` / `rgba16float` fallback (section 8 P3, AC-04).\n * Extends the single ctx input face rather than opening a new loader channel\n * (Pipeline Isolation — inputs declared explicitly).\n */\n readonly transcodeCaps: TranscodeCaps;\n readonly device: unknown;\n}\n\n/**\n * Runtime-side loader injected into the `LoaderRegistry`. One loader per\n * `asset.kind`; the registry dispatches `load` on the kind.\n *\n * `load` is pure of registry bookkeeping (no publication side effect); it only\n * produces the `Asset` POD (or a structured error / `undefined`). See the\n * module comment above for the sync vs async dispatch asymmetry.\n */\nexport interface Loader<P = Asset> {\n readonly kind: string;\n /** Optional Pack v2 dispatch that retains asset-local artifact bytes. */\n readonly loadPack?: (\n input: {\n readonly guid: string;\n readonly kind: string;\n readonly payload: Record<string, unknown>;\n readonly refs: readonly string[];\n readonly artifacts: Readonly<\n Record<\n string,\n {\n readonly descriptor: {\n readonly path: string;\n readonly mediaType: string;\n readonly assetCodec?: {\n readonly name: string;\n readonly container?: 'ktx2' | 'basis';\n readonly profile?: string;\n readonly version?: string;\n };\n };\n readonly bytes: Uint8Array;\n }\n >\n >;\n },\n ctx: LoadContext,\n ) => LoaderOutput<P>;\n load(\n payload: Record<string, unknown>,\n refs: readonly string[] | undefined,\n ctx: LoadContext,\n ): LoaderOutput<P>;\n}\n\nexport type {\n ImportContext,\n ImportDiagnostic,\n ImportDiagnosticLocation,\n ImportErrorCode,\n ImportErrorDetail,\n ImportedArtifactBody,\n ImportedAsset,\n Importer,\n ImporterCapabilities,\n ImportProduct,\n ImportProductFinalizeArtifact,\n ImportProductFinalizeOptions,\n ImportProductFinalizeResult,\n ImportResult,\n ImportSourceRange,\n ImportSubAsset,\n ImportTransport,\n SourceDependency,\n} from './import.js';\nexport {\n IMPORT_ERROR_HINTS,\n ImportError,\n} from './import.js';\n// === EngineMetrics contract SSOT (feat-20260705-runtime-tier2-decomposition M1 / w2, D-3) ===\n//\n// Decision anchors:\n// - plan-strategy D-3 (the 3-method EngineMetrics interface has zero type\n// dependencies; sinking it into @forgeax/engine-types makes types the SSOT\n// leaf. EngineMetricsImpl + createEngineMetrics stay in runtime.)\n// - research F5 (asset-registry.ts `import type { EngineMetrics }` was the\n// third reverse edge from assets cluster to runtime; type-only but the\n// project-reference layer still had to resolve it — relocating the contract\n// to the leaf removes the edge).\n//\n// The interface was originally defined in runtime/src/engine-metrics.ts. It is\n// promoted here so both @forgeax/engine-runtime and @forgeax/engine-assets-runtime\n// import the contract from a single entry point (charter F1).\n\n/**\n * Per-Renderer metrics counter API. Backed by a `Map<string, number>`; reads\n * return a frozen plain object so external mutation never leaks back into the\n * registry (D-5 + R-2 mutation-resistance).\n *\n * Three methods cover the full surface:\n *\n * const r = await createRenderer(canvas);\n * // ... renderer hits some nineslice runtime soft-warn\n * r.metrics.snapshot()['nineslice.scale-too-small']; // -> number | undefined\n *\n * r.metrics.increment('nineslice.scale-too-small'); // mutate counter\n * r.metrics.reset(); // drop all counters\n *\n * Callers can `for (const k in renderer.metrics.snapshot())` to enumerate\n * fired events without knowing the namespace ahead of time.\n *\n * @remarks Closed namespace (charter P5):\n * - `nineslice.scale-too-small`\n * - `nineslice.tile-needs-repeat-sampler`\n * - `render.instancing.foldedDraws`\n */\nexport interface EngineMetrics {\n /**\n * Bump the counter for `name` by 1. Counters start at 0 implicitly; the\n * first `increment(name)` lands a 1 in the snapshot. Names are free-form\n * strings (no prior registration), so feat-local namespaces (e.g.\n * `nineslice.*`) coexist without coordination.\n */\n increment(name: string): void;\n /**\n * Read all counters as an immutable `Readonly<Record<string, number>>`.\n * The returned object is frozen — external mutation throws in strict mode\n * and is silently ignored otherwise. Snapshot-then-mutate is decoupled\n * from the registry: a later `increment` does not retroactively alter the\n * already-returned snapshot.\n */\n snapshot(): Readonly<Record<string, number>>;\n /**\n * Drop every counter back to 0 (counter rows physically removed). Provided\n * for test isolation and Inspector reset workflows; production code on\n * the hot path never calls this.\n */\n reset(): void;\n}\n\n// inspector-client is Node-only (imports 'ws'); consume via\n// import { ... } from '@forgeax/engine-types/inspector-client'\n","import {\n type AssetDecoder,\n type AssetDecoderContribution,\n type AssetKind,\n type AssetLoadError,\n err,\n ok,\n type Result,\n type SceneAsset,\n type SceneEntity,\n type SceneInstanceMount,\n} from '@forgeax/engine-types';\n\nexport const sceneAssetKind: AssetKind<SceneAsset, 'scene'> = {\n kind: 'scene',\n} as AssetKind<SceneAsset, 'scene'>;\n\nfunction invalidScene(guid: string, reason: string): Result<SceneAsset, AssetLoadError> {\n return err({\n code: 'asset-package-invalid',\n expected: 'a scene payload with an entities array',\n hint: 'recook the SceneAsset and publish its complete envelope',\n detail: { guid, reason },\n });\n}\n\ntype SceneWireRefResult =\n | { readonly ok: true; readonly value: SceneAsset }\n | { readonly ok: false; readonly reason: string };\n\n// The Pack envelope owns refs[] while the decoded SceneAsset remains the\n// portable payload. Keep that wire-only fact beside the decoded object so the\n// World-local projection can interpret shared-field indices without putting a\n// component registry or World into the decoder contract.\nconst sceneWireRefs = new WeakMap<object, readonly string[]>();\n\n/** @internal Read the Pack refs[] retained for a decoded SceneAsset payload. */\nexport function sceneAssetWireRefs(asset: SceneAsset): readonly string[] | undefined {\n return sceneWireRefs.get(asset);\n}\n\nfunction resolveWireRef(\n refs: readonly string[],\n value: number,\n location: string,\n): { readonly ok: true; readonly value: string } | { readonly ok: false; readonly reason: string } {\n const guid = refs[value];\n if (!Number.isInteger(value) || value < 0 || guid === undefined) {\n return {\n ok: false,\n reason: `${location} references refs[${value}], but refs contains ${refs.length} entries`,\n };\n }\n return { ok: true, value: guid };\n}\n\nfunction resolveMounts(\n mounts: readonly SceneInstanceMount[] | undefined,\n refs: readonly string[],\n):\n | { readonly ok: true; readonly value: readonly SceneInstanceMount[] | undefined }\n | { readonly ok: false; readonly reason: string } {\n if (mounts === undefined) return { ok: true, value: undefined };\n const resolved: SceneInstanceMount[] = [];\n for (const mount of mounts) {\n if (typeof mount.source !== 'number' || !Number.isInteger(mount.source)) {\n resolved.push(mount);\n continue;\n }\n const ref = resolveWireRef(refs, mount.source, `mount ${mount.localId} source`);\n if (!ref.ok) return ref;\n resolved.push({ ...mount, source: ref.value });\n }\n return { ok: true, value: resolved };\n}\n\nfunction resolveSkinGuids(\n skinGuids: readonly (number | string)[] | undefined,\n refs: readonly string[],\n):\n | { readonly ok: true; readonly value: readonly string[] | undefined }\n | { readonly ok: false; readonly reason: string } {\n if (skinGuids === undefined) return { ok: true, value: undefined };\n const resolved: string[] = [];\n for (let index = 0; index < skinGuids.length; index += 1) {\n const value = skinGuids[index];\n if (typeof value === 'string') {\n resolved.push(value);\n continue;\n }\n if (typeof value !== 'number' || !Number.isInteger(value)) {\n return { ok: false, reason: `skinGuids[${index}] is not a GUID or refs index` };\n }\n const ref = resolveWireRef(refs, value, `skinGuids[${index}]`);\n if (!ref.ok) return ref;\n resolved.push(ref.value);\n }\n return { ok: true, value: resolved };\n}\n\nfunction resolveSceneWireRefs(payload: SceneAsset, refs: readonly string[]): SceneWireRefResult {\n const entities: SceneEntity[] = [];\n for (const entity of payload.entities) {\n const components: Record<string, Record<string, unknown>> = {};\n for (const [componentName, rawFields] of Object.entries(entity.components)) {\n // Component schema lookup is World-local after the ECS core reduction.\n // The runtime projection owns the World-local schema and converts\n // authored GUID fields into World.sharedRefs handles. Keep this loader\n // boundary POD-only instead of consulting a removed process-global ECS\n // component registry.\n components[componentName] = { ...(rawFields as Record<string, unknown>) };\n }\n entities.push({ localId: entity.localId, components });\n }\n\n const mounts = resolveMounts(payload.mounts, refs);\n if (!mounts.ok) return mounts;\n const skinGuids = resolveSkinGuids(\n payload.skinGuids as readonly (number | string)[] | undefined,\n refs,\n );\n if (!skinGuids.ok) return skinGuids;\n\n return {\n ok: true,\n value: {\n kind: 'scene',\n entities,\n ...(mounts.value === undefined ? {} : { mounts: mounts.value }),\n ...(skinGuids.value === undefined ? {} : { skinGuids: skinGuids.value }),\n },\n };\n}\n\n/** Scene owns structural validation; World-local projection resolves shared refs. */\nexport const sceneAssetDecoder: AssetDecoder<SceneAsset> = {\n async decode({ envelope }): Promise<Result<SceneAsset, AssetLoadError>> {\n const payload = envelope.payload;\n if (payload.kind !== 'scene' || !Array.isArray(payload.entities)) {\n return invalidScene(envelope.guid, 'scene payload is missing entities');\n }\n const resolved = resolveSceneWireRefs(payload, envelope.refs);\n if (!resolved.ok) return invalidScene(envelope.guid, resolved.reason);\n sceneWireRefs.set(resolved.value, Object.freeze([...envelope.refs]));\n return ok(resolved.value);\n },\n};\n\nexport const sceneAssetContribution: AssetDecoderContribution<SceneAsset, 'scene'> = {\n kind: sceneAssetKind,\n decoder: sceneAssetDecoder,\n consumer: 'Scene',\n};\n","import type { World } from '@forgeax/engine-ecs';\nimport { componentSchema } from '@forgeax/engine-ecs/internal';\nimport { AssetGuid } from '@forgeax/engine-pack/guid';\nimport {\n type BuiltinAssetKind,\n type ComponentValuesMap,\n err,\n type Handle,\n type MeshAsset,\n type MountOverride,\n ok,\n type Result,\n type SceneAsset,\n type SceneEntity,\n type SceneInstanceMount,\n type SkinAsset,\n toShared,\n} from '@forgeax/engine-types';\nimport { worldSetSceneAssetResolver } from '../instances/scene-instances';\nimport { sceneAssetWireRefs } from './scene-decoder';\n\n/** World resource key for the scene projection's skin dependency view. */\nexport const SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY = 'SceneAssetSkinResolver';\n\n/** World resource key for mesh-owned material default resolution. */\nexport const SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY = 'SceneAssetMeshDefaultResolver';\n\n/**\n * Scene projection output consumed by the renderer's post-instantiate hook.\n * Scene owns the GUID-to-payload dependency view; ECS and Render only depend\n * on this narrow resolver shape.\n */\nexport interface SceneAssetSkinResolver {\n resolveSkinAsset(skeletonHandle: number): SkinAsset | undefined;\n}\n\n/**\n * Render-facing view of material defaults loaded while projecting a scene.\n * MeshAsset keeps these defaults as GUID facts; Render consumes World handles\n * without importing the AssetRegistry or teaching the scene component about\n * renderer policy.\n */\nexport interface SceneAssetMeshDefaultResolver {\n resolveMeshDefaultMaterial(guid: string): number | undefined;\n}\n\n/** Errors raised while projecting a decoded SceneAsset into one World. */\nexport type SceneAssetProjectionError =\n | {\n readonly code: 'scene-reference-target-unsupported';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly guid: string; readonly target: string; readonly field: string };\n }\n | {\n readonly code: 'scene-reference-kind-mismatch';\n readonly expected: string;\n readonly hint: string;\n readonly detail: {\n readonly guid: string;\n readonly target: string;\n readonly expectedKind: BuiltinAssetKind;\n readonly actualKind: string;\n readonly field: string;\n };\n }\n | {\n readonly code: 'scene-reference-cycle';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly cycle: readonly string[] };\n }\n | {\n readonly code: 'scene-reference-unresolved';\n readonly expected: string;\n readonly hint: string;\n readonly detail: { readonly source: string };\n };\n\n/** Loader seam supplied by the current App-owned AssetRegistry. */\nexport type SceneAssetReferenceLoader<E = unknown> = (\n guid: string,\n kind: BuiltinAssetKind,\n) => Promise<Result<unknown, E>>;\n\nconst ASSET_KIND_BY_TARGET: Readonly<Record<string, BuiltinAssetKind>> = Object.freeze({\n MeshAsset: 'mesh',\n TextureAsset: 'texture',\n EquirectAsset: 'equirect',\n SamplerAsset: 'sampler',\n MaterialAsset: 'material',\n SceneAsset: 'scene',\n AudioClipAsset: 'audio',\n SkinAsset: 'skin',\n SkeletonAsset: 'skeleton',\n AnimationClip: 'animation-clip',\n AnimationGraph: 'animation-graph',\n FontAsset: 'font',\n RenderPipelineAsset: 'render-pipeline',\n TilesetAsset: 'tileset',\n VideoAsset: 'video',\n ParticleEffectAsset: 'particle-effect',\n});\n\ntype ProjectError<E> = E | SceneAssetProjectionError;\n\ninterface ProjectionState<E> {\n readonly world: World;\n readonly load: SceneAssetReferenceLoader<E>;\n readonly assetHandles: Map<string, Handle<string, 'shared'>>;\n readonly sceneHandles: Map<string, Handle<'SceneAsset', 'shared'>>;\n readonly skinStore: SceneAssetProjectionStore;\n}\n\ninterface SceneAssetProjectionStore {\n readonly skeletonGuidByHandle: Map<number, string>;\n readonly skinByGuid: Map<string, SkinAsset>;\n readonly skinBySkeletonGuid: Map<string, SkinAsset>;\n readonly resolver: SceneAssetSkinResolver;\n readonly materialHandleByGuid: Map<string, number>;\n readonly meshDefaultResolver: SceneAssetMeshDefaultResolver;\n}\n\nconst projectionStores = new WeakMap<World, SceneAssetProjectionStore>();\n\nfunction projectionStoreFor(world: World): SceneAssetProjectionStore {\n const current = projectionStores.get(world);\n if (current !== undefined) return current;\n\n const skeletonGuidByHandle = new Map<number, string>();\n const skinByGuid = new Map<string, SkinAsset>();\n const skinBySkeletonGuid = new Map<string, SkinAsset>();\n const materialHandleByGuid = new Map<string, number>();\n const resolver: SceneAssetSkinResolver = {\n resolveSkinAsset(skeletonHandle) {\n const skeletonGuid = skeletonGuidByHandle.get(skeletonHandle);\n return skeletonGuid === undefined ? undefined : skinBySkeletonGuid.get(skeletonGuid);\n },\n };\n const meshDefaultResolver: SceneAssetMeshDefaultResolver = {\n resolveMeshDefaultMaterial(guid) {\n return materialHandleByGuid.get(guid.toLowerCase());\n },\n };\n const store = {\n skeletonGuidByHandle,\n skinByGuid,\n skinBySkeletonGuid,\n resolver,\n materialHandleByGuid,\n meshDefaultResolver,\n };\n projectionStores.set(world, store);\n world.insertResource(SCENE_ASSET_SKIN_RESOLVER_RESOURCE_KEY, resolver);\n world.insertResource(SCENE_ASSET_MESH_DEFAULT_RESOLVER_RESOURCE_KEY, meshDefaultResolver);\n return store;\n}\n\nfunction projectionError(\n code: SceneAssetProjectionError['code'],\n detail: SceneAssetProjectionError['detail'],\n): SceneAssetProjectionError {\n switch (code) {\n case 'scene-reference-target-unsupported':\n return {\n code,\n expected: 'every shared SceneAsset field target maps to a built-in Asset kind',\n hint: 'register the target in the AssetTagMap before projecting the scene',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-kind-mismatch':\n return {\n code,\n expected: 'the loaded payload kind matches the component shared-handle target',\n hint: 'repair the scene refs[] edge or republish the referenced asset',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-cycle':\n return {\n code,\n expected: 'an acyclic SceneAsset mount graph',\n hint: 'remove the circular mount.source reference and republish the scenes',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n case 'scene-reference-unresolved':\n return {\n code,\n expected: 'a mount.source GUID that is loadable by the current AssetRegistry',\n hint: 'publish the child SceneAsset before projecting its parent',\n detail: detail as Extract<SceneAssetProjectionError, { code: typeof code }>['detail'],\n };\n }\n}\n\nfunction isPayloadOfKind(\n value: unknown,\n kind: BuiltinAssetKind,\n): value is { readonly kind: string } {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as { readonly kind?: unknown }).kind === kind\n );\n}\n\nfunction sharedTarget(fieldType: unknown): string | undefined {\n if (typeof fieldType !== 'string') return undefined;\n const match = /^shared<([^>]+)>$/.exec(fieldType);\n return match?.[1];\n}\n\nfunction schemaFieldType(field: unknown): unknown {\n if (typeof field === 'object' && field !== null && 'type' in field) {\n return (field as { readonly type?: unknown }).type;\n }\n return field;\n}\n\nfunction sharedArrayTarget(fieldType: unknown): string | undefined {\n if (typeof fieldType !== 'string') return undefined;\n const match = /^array<shared<([^>]+)>(?:,\\s*\\d+)? *>$/.exec(fieldType);\n return match?.[1];\n}\n\nfunction resolveWireValue<E>(\n value: unknown,\n refs: readonly string[] | undefined,\n location: string,\n): Result<unknown, ProjectError<E>> {\n if (refs === undefined || typeof value !== 'number') return ok(value);\n const guid = refs[value];\n if (!Number.isInteger(value) || guid === undefined) {\n return err(\n projectionError('scene-reference-unresolved', {\n source: `${location} refs[${value}]`,\n }),\n );\n }\n return ok(guid);\n}\n\nasync function projectSharedValue<E>(\n state: ProjectionState<E>,\n value: unknown,\n target: string,\n field: string,\n): Promise<Result<unknown, ProjectError<E>>> {\n if (typeof value !== 'string') {\n if (target === 'MeshAsset' && typeof value === 'number') {\n const resolved = state.world.sharedRefs.resolve(toShared<'MeshAsset'>(value));\n if (resolved.ok && isPayloadOfKind(resolved.value, 'mesh')) {\n const meshDefaults = await projectMeshDefaults(state, resolved.value as MeshAsset, field);\n if (!meshDefaults.ok) return meshDefaults;\n }\n }\n return ok(value);\n }\n const kind = ASSET_KIND_BY_TARGET[target];\n if (kind === undefined) {\n return err(\n projectionError('scene-reference-target-unsupported', {\n guid: value,\n target,\n field,\n }),\n );\n }\n\n const cacheKey = `${target}:${value.toLowerCase()}`;\n const cached = state.assetHandles.get(cacheKey);\n if (cached !== undefined) {\n if (target === 'SkeletonAsset') {\n state.skinStore.skeletonGuidByHandle.set(Number(cached), value.toLowerCase());\n }\n return ok(cached);\n }\n\n const loaded = await state.load(value, kind);\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, kind)) {\n return err(\n projectionError('scene-reference-kind-mismatch', {\n guid: value,\n target,\n expectedKind: kind,\n actualKind:\n typeof loaded.value === 'object' && loaded.value !== null && 'kind' in loaded.value\n ? String((loaded.value as { readonly kind: unknown }).kind)\n : typeof loaded.value,\n field,\n }),\n );\n }\n\n if (target === 'MeshAsset') {\n const meshDefaults = await projectMeshDefaults(state, loaded.value as MeshAsset, field);\n if (!meshDefaults.ok) return meshDefaults;\n }\n\n const handle = state.world.allocSharedRef(target, loaded.value);\n state.assetHandles.set(cacheKey, handle);\n if (target === 'SkeletonAsset') {\n state.skinStore.skeletonGuidByHandle.set(Number(handle), value.toLowerCase());\n }\n return ok(handle);\n}\n\nasync function projectMeshDefaults<E>(\n state: ProjectionState<E>,\n mesh: MeshAsset,\n field: string,\n): Promise<Result<void, ProjectError<E>>> {\n if (!Array.isArray(mesh.materialSlots)) return ok(undefined);\n for (let slotIndex = 0; slotIndex < mesh.materialSlots.length; slotIndex += 1) {\n const defaultMaterial = mesh.materialSlots[slotIndex]?.defaultMaterial;\n if (defaultMaterial === undefined) continue;\n const guid = AssetGuid.format(defaultMaterial);\n const projected = await projectSharedValue(\n state,\n guid,\n 'MaterialAsset',\n `${field}.materialSlots[${slotIndex}].defaultMaterial`,\n );\n if (!projected.ok) return projected;\n state.skinStore.materialHandleByGuid.set(guid, Number(projected.value));\n }\n return ok(undefined);\n}\n\nasync function projectSkinDependencies<E>(\n state: ProjectionState<E>,\n asset: SceneAsset,\n): Promise<Result<void, ProjectError<E>>> {\n for (let index = 0; index < (asset.skinGuids?.length ?? 0); index += 1) {\n const guid = asset.skinGuids?.[index];\n if (guid === undefined) continue;\n const guidKey = guid.toLowerCase();\n if (state.skinStore.skinByGuid.has(guidKey)) continue;\n\n const loaded = await state.load(guid, 'skin');\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, 'skin')) {\n return err(\n projectionError('scene-reference-kind-mismatch', {\n guid,\n target: 'SkinAsset',\n expectedKind: 'skin',\n actualKind:\n typeof loaded.value === 'object' && loaded.value !== null && 'kind' in loaded.value\n ? String((loaded.value as { readonly kind: unknown }).kind)\n : typeof loaded.value,\n field: `scene.skinGuids[${index}]`,\n }),\n );\n }\n const skin = loaded.value as SkinAsset;\n state.skinStore.skinByGuid.set(guidKey, skin);\n state.skinStore.skinBySkeletonGuid.set(skin.skeletonGuid.toLowerCase(), skin);\n }\n return ok(undefined);\n}\n\nasync function projectFields<E>(\n state: ProjectionState<E>,\n componentName: string,\n rawFields: Record<string, unknown>,\n location: string,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<Record<string, unknown>, ProjectError<E>>> {\n const component = state.world.components.resolve(componentName);\n if (component === undefined) return ok({ ...rawFields });\n\n const fields: Record<string, unknown> = { ...rawFields };\n for (const [fieldName, value] of Object.entries(rawFields)) {\n const fieldType = schemaFieldType(componentSchema(component)[fieldName]);\n const target = sharedTarget(fieldType);\n if (target !== undefined) {\n const wireValue = await resolveWireValue<E>(\n value,\n wireRefs,\n `${location}.${componentName}.${fieldName}`,\n );\n if (!wireValue.ok) return wireValue;\n const projected = await projectSharedValue(\n state,\n wireValue.value,\n target,\n `${location}.${componentName}.${fieldName}`,\n );\n if (!projected.ok) return projected;\n fields[fieldName] = projected.value;\n continue;\n }\n\n const arrayTarget = sharedArrayTarget(fieldType);\n if (arrayTarget === undefined || !Array.isArray(value)) continue;\n const projectedValues: unknown[] = [];\n for (let index = 0; index < value.length; index += 1) {\n const wireValue = await resolveWireValue<E>(\n value[index],\n wireRefs,\n `${location}.${componentName}.${fieldName}[${index}]`,\n );\n if (!wireValue.ok) return wireValue;\n const projected = await projectSharedValue(\n state,\n wireValue.value,\n arrayTarget,\n `${location}.${componentName}.${fieldName}[${index}]`,\n );\n if (!projected.ok) return projected;\n projectedValues.push(projected.value);\n }\n fields[fieldName] = projectedValues;\n }\n return ok(fields);\n}\n\nasync function projectComponents<E>(\n state: ProjectionState<E>,\n components: Partial<ComponentValuesMap>,\n location: string,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<Partial<ComponentValuesMap>, ProjectError<E>>> {\n const projected: Record<string, Record<string, unknown>> = {};\n for (const [componentName, rawFields] of Object.entries(components)) {\n if (typeof rawFields !== 'object' || rawFields === null || Array.isArray(rawFields)) continue;\n const fields = await projectFields(\n state,\n componentName,\n rawFields as Record<string, unknown>,\n location,\n wireRefs,\n );\n if (!fields.ok) return fields;\n projected[componentName] = fields.value;\n }\n return ok(projected);\n}\n\nasync function projectOverride<E>(\n state: ProjectionState<E>,\n override: MountOverride,\n index: number,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<MountOverride, ProjectError<E>>> {\n const component = state.world.components.resolve(override.comp);\n if (component === undefined) return ok(override);\n\n if (override.field !== undefined) {\n const fieldType = schemaFieldType(componentSchema(component)[override.field]);\n const target = sharedTarget(fieldType);\n if (target !== undefined) {\n const wireValue = await resolveWireValue<E>(\n override.value,\n wireRefs,\n `mount override ${index}.${override.comp}.${override.field}`,\n );\n if (!wireValue.ok) return wireValue;\n const value = await projectSharedValue(\n state,\n wireValue.value,\n target,\n `mount override ${index}.${override.comp}.${override.field}`,\n );\n if (!value.ok) return value;\n return ok({ ...override, value: value.value });\n }\n const arrayTarget = sharedArrayTarget(fieldType);\n if (arrayTarget !== undefined && Array.isArray(override.value)) {\n const values: unknown[] = [];\n for (let element = 0; element < override.value.length; element += 1) {\n const wireValue = await resolveWireValue<E>(\n override.value[element],\n wireRefs,\n `mount override ${index}.${override.comp}.${override.field}[${element}]`,\n );\n if (!wireValue.ok) return wireValue;\n const value = await projectSharedValue(\n state,\n wireValue.value,\n arrayTarget,\n `mount override ${index}.${override.comp}.${override.field}[${element}]`,\n );\n if (!value.ok) return value;\n values.push(value.value);\n }\n return ok({ ...override, value: values });\n }\n return ok(override);\n }\n\n if (\n typeof override.value !== 'object' ||\n override.value === null ||\n Array.isArray(override.value)\n ) {\n return ok(override);\n }\n const value = await projectFields(\n state,\n override.comp,\n override.value as Record<string, unknown>,\n `mount override ${index}`,\n wireRefs,\n );\n if (!value.ok) return value;\n return ok({ ...override, value: value.value });\n}\n\nasync function projectMount<E>(\n state: ProjectionState<E>,\n mount: SceneInstanceMount,\n index: number,\n visiting: Set<string>,\n wireRefs: readonly string[] | undefined,\n): Promise<Result<SceneInstanceMount, ProjectError<E>>> {\n const components =\n mount.components === undefined\n ? undefined\n : await projectComponents(state, mount.components, `mount ${index}`, wireRefs);\n if (components !== undefined && !components.ok) return components;\n\n const overrides: MountOverride[] = [];\n for (let overrideIndex = 0; overrideIndex < (mount.overrides?.length ?? 0); overrideIndex += 1) {\n const override = mount.overrides?.[overrideIndex];\n if (override === undefined) continue;\n const projected = await projectOverride(state, override, overrideIndex, wireRefs);\n if (!projected.ok) return projected;\n overrides.push(projected.value);\n }\n\n if (typeof mount.source !== 'string') {\n return ok({\n ...mount,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n }\n\n const sourceKey = mount.source.toLowerCase();\n const cached = state.sceneHandles.get(sourceKey);\n if (cached !== undefined) {\n return ok({\n ...mount,\n source: cached as unknown as number,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n }\n if (visiting.has(sourceKey)) {\n return err(\n projectionError('scene-reference-cycle', {\n cycle: [...visiting, sourceKey],\n }),\n );\n }\n\n const loaded = await state.load(mount.source, 'scene');\n if (!loaded.ok) return loaded;\n if (!isPayloadOfKind(loaded.value, 'scene')) {\n return err(projectionError('scene-reference-unresolved', { source: mount.source }));\n }\n\n visiting.add(sourceKey);\n const projectedChild = await projectScene(state, loaded.value as SceneAsset, visiting);\n visiting.delete(sourceKey);\n if (!projectedChild.ok) return projectedChild;\n\n const childHandle = state.world.allocSharedRef('SceneAsset', projectedChild.value);\n state.sceneHandles.set(sourceKey, childHandle);\n return ok({\n ...mount,\n source: childHandle as unknown as number,\n ...(components === undefined ? {} : { components: components.value }),\n ...(mount.overrides === undefined ? {} : { overrides }),\n });\n}\n\nasync function projectScene<E>(\n state: ProjectionState<E>,\n asset: SceneAsset,\n visiting: Set<string>,\n): Promise<Result<SceneAsset, ProjectError<E>>> {\n const skins = await projectSkinDependencies(state, asset);\n if (!skins.ok) return skins;\n\n const entities: SceneEntity[] = [];\n const wireRefs = sceneAssetWireRefs(asset);\n for (const entity of asset.entities) {\n const components = await projectComponents(\n state,\n entity.components,\n `entity ${entity.localId as number}`,\n wireRefs,\n );\n if (!components.ok) return components;\n entities.push({ localId: entity.localId, components: components.value });\n }\n\n const mounts: SceneInstanceMount[] = [];\n for (let index = 0; index < (asset.mounts?.length ?? 0); index += 1) {\n const mount = asset.mounts?.[index];\n if (mount === undefined) continue;\n const projected = await projectMount(state, mount, index, visiting, wireRefs);\n if (!projected.ok) return projected;\n mounts.push(projected.value);\n }\n\n return ok({\n kind: 'scene',\n entities,\n ...(asset.mounts === undefined ? {} : { mounts }),\n ...(asset.skinGuids === undefined ? {} : { skinGuids: [...asset.skinGuids] }),\n });\n}\n\n/**\n * Convert decoded GUID fields and nested SceneAsset mounts into references\n * owned by one World. The AssetRegistry remains a loader; this World-facing\n * projection belongs to Scene and is intentionally explicit at each consumer.\n */\nexport async function projectSceneAsset<E = unknown>(\n world: World,\n asset: SceneAsset,\n load: SceneAssetReferenceLoader<E>,\n): Promise<Result<SceneAsset, ProjectError<E>>> {\n const skinStore = projectionStoreFor(world);\n worldSetSceneAssetResolver(world, (source) => {\n if (typeof source === 'number') return ok(toShared<'SceneAsset'>(source));\n return err(projectionError('scene-reference-unresolved', { source }));\n });\n return projectScene(\n {\n world,\n load,\n assetHandles: new Map(),\n sceneHandles: new Map(),\n skinStore,\n },\n asset,\n new Set(),\n );\n}\n","// @forgeax/engine-scene — scene instantiation and instance-state subsystem.\n\nimport {\n type Component,\n type ComponentData,\n type ComponentSchema,\n type EcsError,\n ENTITY_NULL_RAW,\n type EntityHandle,\n type InputShapeOf,\n type ShapeOf,\n type World,\n} from '@forgeax/engine-ecs';\nimport { classifyEntityField, remapEntityFieldValue } from '@forgeax/engine-ecs/externalization';\nimport { componentSchema } from '@forgeax/engine-ecs/internal';\nimport { fillComponentDefaults, StaleEntityError } from '@forgeax/engine-ecs/projection';\nimport type {\n Handle,\n LocalEntityId,\n MountOverride,\n PackErrorCode,\n PackErrorDetail,\n SceneAsset,\n SceneInstanceMount,\n} from '@forgeax/engine-types';\nimport {\n err,\n ok,\n PACK_ERROR_HINTS,\n type Result,\n toUnique,\n unwrapHandle,\n} from '@forgeax/engine-types';\nimport { ComponentNotDefinedError } from '../errors';\n\nconst entityIndex = (entity: EntityHandle): number => (entity as number) & 0x00ffffff;\nconst entityGeneration = (entity: EntityHandle): number => ((entity as number) >>> 24) & 0xff;\n\n/**\n * C-R2 (feat-20260622-s5 / studio-issues): one structured, non-fatal record of\n * a SceneAsset payload field that did NOT match the target component's schema.\n *\n * Scene data is loader-fed and may carry a stale / deprecated / typo'd field\n * (an editor renames a field, an old `.pack.json` lags). `worldInstantiateScene`\n * does NOT blank the whole scene over one such field (#478 lesson: a\n * prod-silent strip re-introduced an invisible-entity class) and does NOT abort\n * fatally. Instead it skips the unknown key (no write, no input mutation) and\n * surfaces this record on the success value's `diagnostics[]` — observable in\n * production (NOT NODE_ENV-gated), consumed by property access (no string parse):\n *\n * const r = worldInstantiateScene(world, handle);\n * if (r.ok) for (const d of r.value.diagnostics)\n * console.warn(`unknown field ${d.component}.${d.field} on localId ${d.localId}`);\n *\n * Direct `world.spawn` / `world.addComponent` / `Commands.spawn` stay fail-fast\n * with `SpawnDataUnknownFieldError` — those are explicit API calls where a typo\n * is a programming error, not loader-fed data.\n */\nexport type SceneInstantiateDiagnostic = {\n /** Component name (schema key) the unknown field appeared under. */\n readonly component: string;\n /** The offending field name not declared in the component schema. */\n readonly field: string;\n /** LocalEntityId (within its owning SceneAsset) of the carrying entity. */\n readonly localId: number;\n};\n\n/**\n * Success value of `worldInstantiateScene`. `root` is the synthetic scene-root\n * EntityHandle (carries `SceneInstance`); `diagnostics` is the (possibly empty)\n * list of non-fatal unknown-field records aggregated across this scene and every\n * recursively mounted sub-scene (C-R2). Empty array = no diagnostics.\n */\nexport type SceneInstantiateOk = {\n readonly root: EntityHandle;\n readonly diagnostics: readonly SceneInstantiateDiagnostic[];\n};\n\n/**\n * Success value of `worldInstantiateSceneFlat` — the \"edit the scene itself\"\n * primitive. Unlike `instantiateScene`, NO synthetic SceneInstance root is\n * minted and NO `ChildOf` is forced onto top-level members: the scene's own\n * entities become plain top-level world entities whose hierarchy is exactly\n * their authored `ChildOf` (an entity with no `ChildOf` stays a root). `roots`\n * is the set of those top-level handles (own rootless entities + top-level\n * mount carriers). Nested prefabs inside the scene STILL materialise as their\n * own SceneInstance anchors (charter P4: instance == entity-with-SceneInstance)\n * — only THIS scene is flat.\n */\nexport type SceneInstantiateFlatOk = {\n readonly roots: EntityHandle[];\n /**\n * All mount carrier entities spawned while flattening this scene. These are\n * separate from `roots`: carriers with an authored parent are not roots,\n * but still delimit a nested prefab subtree for post-spawn hooks.\n */\n readonly mountEntities: EntityHandle[];\n readonly diagnostics: readonly SceneInstantiateDiagnostic[];\n};\n\n/**\n * @internal Intermediate produced by `_spawnSceneMembers` and consumed by both\n * the anchor finisher (`_instantiateSceneAsset`) and the flat finisher\n * (`_instantiateSceneAssetFlat`). Holds everything the shared member-spawn\n * (mounts recursion + own-entity spawn + deferred owned-parent wiring) computes,\n * before either finisher decides whether to wrap the members in a synthetic\n * SceneInstance root.\n */\nexport interface SceneMembersSpawn {\n /** LocalEntityId → live Entity u32 (ENTITY_NULL_RAW for unspawned slots). */\n readonly mapping: Uint32Array;\n /** Reverse map live Entity → LocalEntityId for override / detach bookkeeping. */\n readonly entityToLocalId: Map<EntityHandle, LocalEntityId>;\n /** Own entities that carried no `ChildOf` — the scene's authored top-level roots. */\n readonly rootEntities: EntityHandle[];\n /** Mount carriers whose `mount.parent === undefined` (default-parented). */\n readonly mountEntitiesNeedingRootParent: EntityHandle[];\n /** Every mount carrier spawned by this scene, including explicitly parented carriers. */\n readonly mountEntities: EntityHandle[];\n /**\n * The child anchor and mapping for each mount. Flat scene opening has no\n * outer SceneInstance state to own parent-namespace mount overrides, so it\n * records those overrides on this child anchor after the shared spawn pass.\n */\n readonly mountInstances: readonly {\n readonly mount: SceneInstanceMount;\n readonly root: EntityHandle;\n readonly mapping: Uint32Array;\n }[];\n /** `entities.length + mounts + Σ memberCount`, captured at instantiate-time. */\n readonly totalSlots: number;\n}\n\nexport type SceneAssetResolver = (\n source: number | string,\n parentHandle: Handle<'SceneAsset', 'shared'>,\n) => Result<Handle<'SceneAsset', 'shared'>, unknown>;\n\ninterface SceneWorldState {\n resolver: SceneAssetResolver | null;\n readonly statePayloads: Map<number, unknown>;\n instantiateHook: SceneInstantiateHook | null;\n}\n\nexport type SceneInstantiateHook = (\n world: World,\n root: EntityHandle,\n) => { readonly ok: true } | { readonly ok: false; readonly error: unknown };\n\nconst sceneWorldStates = new WeakMap<World, SceneWorldState>();\n\nfunction sceneWorldState(world: World): SceneWorldState {\n const current = sceneWorldStates.get(world);\n if (current !== undefined) return current;\n const created = {\n resolver: null,\n statePayloads: new Map<number, unknown>(),\n instantiateHook: null,\n };\n sceneWorldStates.set(world, created);\n return created;\n}\n\n/** @internal */\nexport function worldSetSceneAssetResolver(world: World, resolver: SceneAssetResolver): void {\n sceneWorldState(world).resolver = resolver;\n}\n\n/** @internal */\nexport function worldGetSceneAssetResolver(world: World): SceneAssetResolver | null {\n return sceneWorldState(world).resolver;\n}\n\n/** @internal Install the owner post-instantiate boundary for one World. */\nexport function worldSetSceneInstantiateHook(\n world: World,\n hook: SceneInstantiateHook | null,\n): void {\n sceneWorldState(world).instantiateHook = hook;\n}\n\n/**\n * Materialise a SceneAsset (and any nested SceneAsset references via\n * `mounts[]`) into live entities. Returns the synthetic root Entity that\n * carries the `SceneInstance` ECS component (charter P4: instance ==\n * entity-with-SceneInstance).\n *\n * Recursion path is closed inside `_instantiateSceneRec(handle, parent,\n * stack)` (D-3); cycle detection is fail-fast `pack-cyclic-reference +\n * detail.kind:'mount-asset'` (D-1 mirror, plan-strategy §D-3). The\n * caller-supplied `parent` flows to the synthetic root's `ChildOf` so the\n * full sub-tree attaches under the AI user's host entity.\n *\n * @example\n * const r = worldInstantiateScene(world, handle);\n * if (!r.ok) return r;\n * const { root, diagnostics } = r.value;\n * for (const d of diagnostics) // C-R2: unknown-field records, non-fatal\n * console.warn(`unknown field ${d.component}.${d.field} on localId ${d.localId}`);\n * const inst = world.get(root, SceneInstance).value;\n * const member = inst.mapping[0]; // first member entity\n */\nexport function worldInstantiateScene(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n parent?: EntityHandle,\n): Result<SceneInstantiateOk, EcsError> {\n const stack = new Set<number>();\n // C-R2: collect non-fatal unknown-field diagnostics across this scene and\n // every recursively mounted sub-scene. The internal recursion writes into\n // this accumulator; only the public entry packages it onto the success value.\n const diagnostics: SceneInstantiateDiagnostic[] = [];\n const r = worldInstantiateSceneRec(world, handle, parent, stack, diagnostics);\n if (!r.ok) return r;\n const hook = sceneWorldState(world).instantiateHook;\n if (hook !== null) {\n const hooked = hook(world, r.value);\n if (!hooked.ok) {\n worldDespawnScene(world, r.value);\n return err(hooked.error as EcsError);\n }\n }\n return ok({ root: r.value, diagnostics });\n}\n/**\n * Materialise a SceneAsset FLAT — the \"edit the scene itself\" primitive.\n * Unlike `instantiateScene`, this mints NO synthetic SceneInstance root and\n * forces NO `ChildOf` onto top-level members: the scene's own entities become\n * plain top-level world entities whose hierarchy is exactly their authored\n * `ChildOf` (an entity with no `ChildOf` is a root). Use this to OPEN a scene\n * for editing; use `instantiateScene` (anchor) at runtime / for nested\n * prefabs where an instance boundary + override isolation is wanted.\n *\n * Nested prefabs referenced via `mounts[]` STILL materialise as their own\n * SceneInstance anchors (charter P4 preserved) — only THIS top scene is flat.\n *\n * @example\n * const r = worldInstantiateSceneFlat(world, handle);\n * if (!r.ok) return r;\n * const { roots, diagnostics } = r.value; // roots = top-level handles\n */\nexport function worldInstantiateSceneFlat(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n): Result<SceneInstantiateFlatOk, EcsError> {\n const stack = new Set<number>();\n const diagnostics: SceneInstantiateDiagnostic[] = [];\n const handleKey = unwrapHandle(handle);\n const resolved = worldResolveSceneAsset(world, handle);\n if (!resolved.ok) return resolved;\n stack.add(handleKey);\n let r: Result<{ roots: EntityHandle[]; mountEntities: EntityHandle[] }, EcsError>;\n try {\n r = worldInstantiateSceneAssetFlat(world, handle, resolved.value, stack, diagnostics);\n } finally {\n stack.delete(handleKey);\n }\n if (!r.ok) return r;\n return ok({ ...r.value, diagnostics });\n}\n/**\n * @internal Recursive helper carrying the cycle-detection stack. Sugar /\n * other public callers must not see this mechanic — use `instantiateScene`\n * (D-3 / charter P1).\n */\nexport function worldInstantiateSceneRec(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n parent: EntityHandle | undefined,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const handleKey = unwrapHandle(handle);\n if (stack.has(handleKey)) {\n const cycleArr: string[] = [];\n for (const k of stack) cycleArr.push(String(k));\n cycleArr.push(String(handleKey));\n const detail: PackErrorDetail = {\n code: 'pack-cyclic-reference',\n kind: 'mount-asset',\n cycle: cycleArr,\n };\n return err({\n code: 'pack-cyclic-reference' as PackErrorCode,\n expected: 'acyclic SceneAsset mount graph',\n hint: PACK_ERROR_HINTS['pack-cyclic-reference'],\n detail,\n } as unknown as EcsError);\n }\n const resolved = worldResolveSceneAsset(world, handle);\n if (!resolved.ok) return resolved;\n const asset = resolved.value;\n stack.add(handleKey);\n try {\n return worldInstantiateSceneAsset(world, handle, asset, parent, stack, diagnostics);\n } finally {\n stack.delete(handleKey);\n }\n}\n/**\n * @internal Resolve a SceneAsset handle through the SharedRefStore.\n * The handle u32 is the SharedRefStore slot id (`world.allocSharedRef\n * ('SceneAsset', asset)` is the producer; rc starts at 1, the SceneInstance\n * spawn retains to rc=2 in M4 / w13). Errors propagate as EcsError so the\n * instantiateScene chain returns a single closed union.\n */\nexport function worldResolveSceneAsset(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n): Result<SceneAsset, EcsError> {\n const r = world.sharedRefs.resolve(handle);\n if (!r.ok) {\n return err(r.error as unknown as EcsError);\n }\n return ok(r.value as SceneAsset);\n}\n/**\n * @internal Spawn one SceneAsset's members — the shared body of both scene\n * finishers. Recurses into `mounts[]` (each nested prefab becomes its own\n * SceneInstance anchor), spawns `entities[]` honouring their authored\n * `ChildOf`, and wires deferred owned-parent mount edges. Does NOT create a\n * synthetic root or force any `ChildOf` — that is the caller's (finisher's)\n * job. `_instantiateSceneRec` owns cycle bookkeeping.\n */\nexport function worldSpawnSceneMembers(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<SceneMembersSpawn, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const childOfToken = world.components.resolve('ChildOf');\n // ChildOf is optional — only needed if the asset declares ChildOf or a\n // caller-supplied parent must be wired. If absent and we need it, we\n // fail-fast at the wiring site below.\n\n const ownEntities = asset.entities;\n const ownMounts = asset.mounts ?? [];\n const memberSum = ownMounts.reduce((s, m) => s + m.memberCount, 0);\n const countBaseline = ownEntities.length + ownMounts.length + memberSum;\n // C-R1 (studio-issues #6): mapping table must be sized to maxLocalId+1,\n // not to the entity count. An editor scene may have non-contiguous\n // localIds (deleted entities leave gaps); sizing to count means any\n // localId >= count is a silent Uint32Array OOB no-op -> entity spawns\n // but is unreachable by localId -> users report \"character can't move\".\n // Take the max of count-baseline and id-range so both packed and\n // sparse scenes work without over-allocation in the common case.\n let maxLocalId = ownEntities.reduce((m, e) => Math.max(m, e.localId as unknown as number), -1);\n for (const mount of ownMounts) {\n maxLocalId = Math.max(maxLocalId, mount.localId as unknown as number);\n const last = (mount.memberFirst as unknown as number) + mount.memberCount - 1;\n maxLocalId = Math.max(maxLocalId, last);\n }\n const totalSlots = Math.max(countBaseline, maxLocalId + 1);\n\n // R2/Bonus: namespace-overlap fail-fast (AC-05 /\n // pack-mount-localid-overlap). Each LocalEntityId in\n // [0, totalSlots) must be claimed by exactly one of:\n // - entities[i].localId\n // - mounts[i].localId\n // - mounts[i] window slot (memberFirst .. memberFirst+memberCount-1)\n // Overlap or duplicate claim => fail-fast with the offending localIds\n // and human-readable origin labels.\n {\n const claims = new Map<number, string>();\n const overlapLids = new Set<number>();\n const overlapSources: string[] = [];\n const claim = (lid: number, src: string): void => {\n const prior = claims.get(lid);\n if (prior !== undefined) {\n if (!overlapLids.has(lid)) {\n overlapLids.add(lid);\n overlapSources.push(prior);\n overlapSources.push(src);\n } else {\n overlapSources.push(src);\n }\n return;\n }\n claims.set(lid, src);\n };\n for (const ent of ownEntities) {\n claim(ent.localId as unknown as number, `entities[${ent.localId as unknown as number}]`);\n }\n for (const mount of ownMounts) {\n const mLid = mount.localId as unknown as number;\n claim(mLid, `mount[${mLid}]`);\n const first = mount.memberFirst as unknown as number;\n for (let k = 0; k < mount.memberCount; k += 1) {\n claim(first + k, `mount[${mLid}].member[${k}]`);\n }\n }\n if (overlapLids.size > 0) {\n const overlapping = Array.from(overlapLids).sort((a, b) => a - b);\n return err({\n code: 'pack-mount-localid-overlap' as PackErrorCode,\n expected: 'each LocalEntityId claimed by exactly one entity or mount slot',\n hint: PACK_ERROR_HINTS['pack-mount-localid-overlap'],\n detail: {\n code: 'pack-mount-localid-overlap',\n overlapping,\n sources: overlapSources,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n\n // Slot table: indexed by LocalEntityId; populated as entities / mounts /\n // members are spawned. mapping[localId] = encoded Entity u32. Unspawned\n // slots hold ENTITY_NULL_RAW (0xffffffff) — NOT 0, because a fresh World's\n // first spawn encodes to gen=0+idx=0=raw 0, which is a valid Entity. The\n // remap path in `_buildSceneEntityComponentDatas` distinguishes the two\n // (live=ENTITY_NULL_RAW => parent unspawned at remap time => surface as\n // null sentinel; live=any other u32 => valid live Entity, including 0).\n const mapping = new Uint32Array(totalSlots).fill(ENTITY_NULL_RAW);\n const entityToLocalId = new Map<EntityHandle, LocalEntityId>();\n const rootEntities: EntityHandle[] = [];\n const mountEntities: EntityHandle[] = [];\n // R2/B-1: mount entities whose `mount.parent === undefined` need their\n // ChildOf wired to the outer synthetic root (this scene's root). Step 5\n // does the wiring once the synthetic root entity is materialised; we\n // collect them here in step 1.\n const mountEntitiesNeedingRootParent: EntityHandle[] = [];\n // D-8 (feat-20260707): mount entities whose `mount.parent` points at an\n // OWNED entity slot are wired AFTER step 2 spawns the owned entities —\n // mounts are processed first (step 1), so the owned parent slot is still\n // ENTITY_NULL_RAW at mount-processing time. Same deferred-wiring shape as\n // mountEntitiesNeedingRootParent: register [mountEntity, parentSlot] here,\n // wire ChildOf once the slot is live. Without this the edge was silently\n // dropped, and the mount carrier stayed unreachable from its owned parent.\n const mountEntitiesNeedingDeferredParent: Array<[EntityHandle, number]> = [];\n const mountInstances: Array<{\n readonly mount: SceneInstanceMount;\n readonly root: EntityHandle;\n readonly mapping: Uint32Array;\n }> = [];\n\n // 1. Recurse into mounts[] FIRST so the mount-window slots\n // (`mount.localId` + `[memberFirst, memberFirst+memberCount)`) are\n // populated before any owned entity tries to remap a LocalEntityId\n // pointing into the mount window (AC-24 cross-boundary reference).\n for (const mount of ownMounts) {\n // R2/B-3 + R2/B-4: validate overrides BEFORE child resolution so a\n // malformed override fails fast without observable side-effects.\n const overrideValidationRes = worldValidateMountOverrides(world, mount);\n if (!overrideValidationRes.ok) {\n return overrideValidationRes;\n }\n\n // Spawn the mount entity (carries mount.components).\n const mountLid = mount.localId as unknown as number;\n const mountSpawnRes = worldSpawnMountEntity(world, mount, mapping, diagnostics);\n if (!mountSpawnRes.ok) return mountSpawnRes;\n const mountEntity = mountSpawnRes.value;\n mountEntities.push(mountEntity);\n mapping[mountLid] = mountEntity as unknown as number;\n\n // Resolve mount.source -> child SceneAsset handle.\n const childHandleRes = worldResolveMountSource(world, mount.source, handle);\n if (!childHandleRes.ok) return childHandleRes;\n const childHandle = childHandleRes.value;\n\n // Recursively instantiate the child. Its synthetic root attaches as a\n // child of the mount entity. The child writes its own unknown-field\n // diagnostics into the SAME accumulator, so they bubble to the top-level\n // instantiateScene result (C-R2 recursive aggregation).\n const childRes = worldInstantiateSceneRec(world, childHandle, mountEntity, stack, diagnostics);\n if (!childRes.ok) return childRes;\n\n // R2/B-2: cross-check mount.memberCount === child.totalSlots BEFORE\n // copying the mount window. The child SceneInstance.mapping length is\n // the authoritative `totalSlots` of the child. AC-04 / requirements\n // S-5 mandate fail-fast at runtime for this disagreement.\n const childInstRes = world.get(childRes.value, sceneInstanceToken);\n if (!childInstRes.ok) return childInstRes;\n const childMapping = (childInstRes.value as unknown as { mapping: Uint32Array }).mapping;\n mountInstances.push({ mount, root: childRes.value, mapping: childMapping });\n if (childMapping.length !== mount.memberCount) {\n return err({\n code: 'pack-mount-count-mismatch' as PackErrorCode,\n expected: 'mount.memberCount === child SceneAsset totalSlots',\n hint: PACK_ERROR_HINTS['pack-mount-count-mismatch'],\n detail: {\n code: 'pack-mount-count-mismatch',\n mountLocalId: mountLid,\n declared: mount.memberCount,\n actual: childMapping.length,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n\n // Pull the child's mapping into our parent window. Default unset slots\n // to ENTITY_NULL_RAW so downstream \"live\" checks distinguish them from\n // the first Entity (gen=0+idx=0 encodes to raw u32 0).\n const window = mount.memberCount;\n for (let k = 0; k < window; k += 1) {\n mapping[(mount.memberFirst as unknown as number) + k] = childMapping[k] ?? ENTITY_NULL_RAW;\n }\n\n // Apply mount.overrides at instantiate-time (AC-19).\n // Each override.localId addresses a slot in *this* (parent) namespace\n // (R2/F-8 cement: parent-namespace + memberFirst+offset addressing).\n // The state map will be populated below with these overrides — but we\n // must also write the value through to the live entity column so the\n // readback invariant holds.\n // Mount-entity itself never has children attached by the caller other\n // than via the recursive child; nothing else to wire here.\n if (childOfToken !== undefined) {\n if (mount.parent !== undefined) {\n // Reparent the mount-entity ChildOf to the caller-specified parent.\n const parentSlot = mount.parent as unknown as number;\n const parentEntity = mapping[parentSlot];\n if (parentEntity !== undefined && parentEntity !== ENTITY_NULL_RAW) {\n const r = world.addComponent(mountEntity, {\n component: childOfToken,\n data: { parent: parentEntity } as never,\n });\n if (!r.ok) {\n // ChildOf may already be present from layer-1; reparent via set.\n const set = world.set(mountEntity, childOfToken, {\n parent: parentEntity,\n } as never);\n if (!set.ok) return set as Result<SceneMembersSpawn, EcsError>;\n }\n } else {\n // D-8: the owned parent slot is not spawned yet (owned entities\n // spawn in step 2, after this mount loop). Defer the ChildOf wire\n // to step 2's tail once mapping[parentSlot] is live.\n mountEntitiesNeedingDeferredParent.push([mountEntity, parentSlot]);\n }\n } else {\n // R2/B-1: default semantic — mount.parent === undefined wires the\n // mount entity ChildOf to *this* scene's synthetic root (created\n // in step 3 below). Defer the actual wire to step 5 after the\n // synthetic root spawn; record the mount entity here.\n mountEntitiesNeedingRootParent.push(mountEntity);\n }\n }\n }\n\n // 2. Spawn entities[] entities. Topo-sort by ChildOf so parents are\n // spawned before children (so localId remap can read mapping live).\n // This runs AFTER mount processing (step 1) so cross-boundary\n // `ChildOf {parent: <mount-window-localId>}` references resolve\n // correctly (AC-24).\n const order = sceneTopoSort(ownEntities);\n for (const idx of order) {\n const node = ownEntities[idx];\n if (node === undefined) continue;\n const lid = node.localId as unknown as number;\n const compDataRes = worldBuildSceneEntityComponentDatas(world, node, mapping, diagnostics);\n if (!compDataRes.ok) return compDataRes;\n const sp = (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(\n ...compDataRes.value,\n );\n if (!sp.ok) return sp as Result<SceneMembersSpawn, EcsError>;\n const e = sp.value;\n mapping[lid] = e as unknown as number;\n entityToLocalId.set(e, lid as unknown as LocalEntityId);\n if (node.components.ChildOf === undefined) {\n rootEntities.push(e);\n }\n }\n\n // 2b. D-8 (feat-20260707): wire deferred owned-parent mount ChildOf edges.\n // Owned entities are now live (step 2 above), so mapping[parentSlot]\n // resolves. Same shape as the mountEntitiesNeedingRootParent wiring in\n // step 5. The relationship mirror hook (relationshipOnInsert) pushes the\n // carrier into the owned parent's Children mirror automatically.\n if (childOfToken !== undefined) {\n for (const [mountEntity, parentSlot] of mountEntitiesNeedingDeferredParent) {\n const parentEntity = mapping[parentSlot];\n if (parentEntity === undefined || parentEntity === ENTITY_NULL_RAW) continue;\n const set = world.set(mountEntity, childOfToken, { parent: parentEntity } as never);\n if (!set.ok) {\n const r = world.addComponent(mountEntity, {\n component: childOfToken,\n data: { parent: parentEntity } as never,\n });\n if (!r.ok) return r as Result<SceneMembersSpawn, EcsError>;\n }\n }\n }\n\n return ok({\n mapping,\n entityToLocalId,\n rootEntities,\n mountEntitiesNeedingRootParent,\n mountEntities,\n mountInstances,\n totalSlots,\n });\n}\n/**\n * @internal Spawn one SceneAsset's entities + apply mounts recursively, then\n * wrap them in a synthetic SceneInstance root (the anchor). This is the\n * runtime / Play / nested-mount finisher (charter P4: instance ==\n * entity-with-SceneInstance). Caller (`_instantiateSceneRec`) owns cycle\n * bookkeeping.\n */\nexport function worldInstantiateSceneAsset(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n parent: EntityHandle | undefined,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const childOfToken = world.components.resolve('ChildOf');\n\n const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);\n if (!membersRes.ok) return membersRes;\n const { mapping, entityToLocalId, rootEntities, mountEntitiesNeedingRootParent, totalSlots } =\n membersRes.value;\n const { mountInstances } = membersRes.value;\n const ownMounts = asset.mounts ?? [];\n\n // 3. Spawn the synthetic root entity carrying SceneInstance.\n // First alloc the state ref so the SceneInstance.state column has a\n // live u32; then attach SceneInstance to a fresh entity.\n let stateRef: Handle<'SceneInstanceState', 'unique'>;\n stateRef = world.allocUniqueRef('SceneInstanceState', null, () => {\n sceneWorldState(world).statePayloads.delete(Number(stateRef));\n });\n // Spawn the root with SceneInstance component, mapping snapshot, and\n // state ref. The mapping is a Uint32Array (array<entity> field shape).\n // Convert mapping Uint32Array to plain number[] for spawn write — the\n // ECS array<entity> arm copies element-by-element and accepts both, but\n // the plain-array form sidesteps a Uint32Array.length=0 corner case\n // observed during M2 testing where a non-empty Uint32Array was written\n // as if empty (suspect: archetype write-array dispatch on instanceof\n // Array vs TypedArray).\n const mappingPlain: number[] = Array.from(mapping);\n // The synthetic root is the ChildOf parent of every owned root entity\n // (step 5 below) and may itself become a ChildOf parent of a caller-\n // supplied `parent` chain. propagateTransforms walks ChildOf parents\n // through the Transform liveMap and treats a parent missing Transform\n // as `hierarchy-broken`, so the synthetic root must carry Transform\n // (identity TRS via layer-2 defaults) when Transform is defined.\n const rootComponents: ComponentData[] = [\n {\n component: sceneInstanceToken,\n data: {\n source: handle,\n mapping: mappingPlain,\n state: stateRef,\n } as never,\n },\n ];\n const transformToken = world.components.resolve('Transform');\n if (transformToken !== undefined) {\n rootComponents.push({\n component: transformToken,\n data: {} as never,\n });\n }\n const rootSpawn = (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(\n ...rootComponents,\n );\n if (!rootSpawn.ok) {\n return rootSpawn;\n }\n const rootEntity = rootSpawn.value;\n\n // 4. Build SceneInstanceState payload + register it in the UniqueRefStore\n // under the same handle. We use the public `_setUniqueRefPayload`\n // helper (added below) so the alloc -> populate sequence stays atomic.\n const overrides = new Map<LocalEntityId, Map<string, MountOverride>>();\n for (const mount of ownMounts) {\n for (const ov of mount.overrides ?? []) {\n // feat-20260713 M2 / w8: `MountOverride.field` is optional (add-or-patch\n // discriminant carried by the shape itself). Record the override into\n // the SceneInstanceState map keyed by comp (no field) or comp:field\n // (field-patch), then apply it to the live member column via the shared\n // add-or-patch helper.\n const lid = ov.localId as unknown as LocalEntityId;\n let fieldMap = overrides.get(lid);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n overrides.set(lid, fieldMap);\n }\n fieldMap.set(mountOverrideStateKey(ov), ov);\n // Apply override to the live member entity column.\n const memberEntityRaw = mapping[lid as unknown as number];\n if (memberEntityRaw !== undefined && memberEntityRaw !== ENTITY_NULL_RAW) {\n const memberEntity = memberEntityRaw as unknown as EntityHandle;\n const applyRes = worldApplyMountOverride(world, memberEntity, ov);\n if (!applyRes.ok) {\n return applyRes as Result<EntityHandle, EcsError>;\n }\n }\n }\n }\n\n const detached = new Set<LocalEntityId>();\n const state: Record<string, unknown> = {\n source: handle,\n entityToLocalId,\n detachedLocalIds: detached,\n // Convert overrides Map<LocalEntityId, Map<string, MountOverride>>\n // into Map<LocalEntityId, Map<string, SceneInstanceOverrideRecord>>\n overrides: worldMountOverridesToStateMap(overrides),\n rootEntities,\n mountRoots: mountInstances.map(({ root }) => root),\n totalSlots,\n mountTimeOverrides: ownMounts.flatMap((m) => m.overrides ?? []),\n };\n // Stuff the state into the UniqueRefStore under the existing slot. We\n // re-use the slot we allocated above by writing directly into the\n // payloads map via a `_setUniqueRefPayload` shim.\n worldSetUniqueRefPayload(world, stateRef, state);\n\n // 5. Wire ChildOf for every owned root entity (no ChildOf at layer-1)\n // to the synthetic root.\n if (childOfToken !== undefined) {\n for (const rootE of rootEntities) {\n const has = world.get(rootE, childOfToken);\n if (!has.ok) {\n // No ChildOf yet — attach to synthetic root.\n const r = world.addComponent(rootE, {\n component: childOfToken,\n data: { parent: rootEntity } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n // R2/B-1: wire mount entities with default `mount.parent === undefined`\n // to this scene's synthetic root. _spawnMountEntity may have attached a\n // placeholder ChildOf {parent: ENTITY_NULL_RAW} when mount.components\n // was empty; overwrite via set so the ChildOf chain meshRenderer ->\n // childSyntheticRoot -> mountEntity -> outerSyntheticRoot resolves\n // through Transform-bearing parents (AC-16 / requirements S-7).\n for (const mountE of mountEntitiesNeedingRootParent) {\n const set = world.set(mountE, childOfToken, { parent: rootEntity } as never);\n if (!set.ok) {\n const r = world.addComponent(mountE, {\n component: childOfToken,\n data: { parent: rootEntity } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n // Caller-supplied parent: synthetic root's ChildOf -> parent.\n if (parent !== undefined) {\n const r = world.addComponent(rootEntity, {\n component: childOfToken,\n data: { parent } as never,\n });\n if (!r.ok) return r as Result<EntityHandle, EcsError>;\n }\n }\n\n return ok(rootEntity);\n}\n/**\n * @internal Flat finisher — spawn one SceneAsset's members WITHOUT wrapping\n * them in a synthetic SceneInstance root and WITHOUT forcing `ChildOf` onto\n * top-level members. Used for \"opening a scene to edit\": the scene's own\n * entities become plain top-level world entities whose hierarchy is exactly\n * their authored `ChildOf`. Nested prefabs inside still materialise as their\n * own SceneInstance anchors (the mount recursion in `_spawnSceneMembers` is\n * always anchored). Returns the top-level handles (own rootless entities +\n * top-level mount carriers).\n */\nexport function worldInstantiateSceneAssetFlat(\n world: World,\n handle: Handle<'SceneAsset', 'shared'>,\n asset: SceneAsset,\n stack: Set<number>,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<{ roots: EntityHandle[]; mountEntities: EntityHandle[] }, EcsError> {\n const membersRes = worldSpawnSceneMembers(world, handle, asset, stack, diagnostics);\n if (!membersRes.ok) return membersRes;\n const { rootEntities, mountEntitiesNeedingRootParent, mountEntities, mountInstances } =\n membersRes.value;\n const childOfToken = world.components.resolve('ChildOf');\n\n // Apply parent mount overrides to the live columns and record them on the\n // nested child anchor. Flat mode has no outer SceneInstance state; without\n // this hand-authored mounts[].overrides affect the live value but disappear\n // from the child state, so Gateway re-open cannot discover or revert them.\n for (const { mount, root, mapping: childMapping } of mountInstances) {\n const childStateRes = worldGetSceneInstanceState(world, root);\n if (!childStateRes.ok) return childStateRes;\n for (const ov of mount.overrides ?? []) {\n const childLocalId =\n (ov.localId as unknown as number) - (mount.memberFirst as unknown as number);\n const memberEntityRaw = childMapping[childLocalId];\n if (memberEntityRaw === undefined || memberEntityRaw === ENTITY_NULL_RAW) continue;\n const memberEntity = memberEntityRaw as unknown as EntityHandle;\n const applyRes = worldApplyMountOverride(world, memberEntity, ov);\n if (!applyRes.ok) {\n return applyRes as Result<\n { roots: EntityHandle[]; mountEntities: EntityHandle[] },\n EcsError\n >;\n }\n let fieldMap = childStateRes.value.overrides.get(childLocalId as LocalEntityId);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n childStateRes.value.overrides.set(childLocalId as LocalEntityId, fieldMap);\n }\n fieldMap.set(mountOverrideStateKey(ov), {\n comp: ov.comp,\n ...(ov.field === undefined ? {} : { field: ov.field }),\n value: ov.value,\n });\n }\n }\n\n // Default-parented mount carriers (`mount.parent === undefined`) would, in\n // anchor mode, attach to the synthetic root. Flat mode has none, so they\n // stay top-level. `_spawnMountEntity` may have left a placeholder\n // `ChildOf {parent: ENTITY_NULL_RAW}` (rare: mount with no components AND\n // Transform unregistered) — strip it so the carrier is a genuine root.\n if (childOfToken !== undefined) {\n for (const mountE of mountEntitiesNeedingRootParent) {\n const co = world.get(mountE, childOfToken);\n if (co.ok && (co.value as { parent: number }).parent === ENTITY_NULL_RAW) {\n world.removeComponent(mountE, childOfToken);\n }\n }\n }\n\n return ok({ roots: [...rootEntities, ...mountEntitiesNeedingRootParent], mountEntities });\n}\n/** @internal Build ComponentData[] for one SceneEntity, remapping localIds.\n *\n * C-R2 (feat-20260622-s5 M6): unknown fields on a SceneAsset payload are NOT\n * fatal. Unlike `world.spawn` (an explicit API call where a typo is a\n * programming error -> `SpawnDataUnknownFieldError`), scene data is loader-fed\n * and may carry a stale / deprecated / typo'd field. The remap below builds a\n * fresh `remappedRaw` and simply SKIPS keys absent from the schema (no input\n * mutation — the source `raw` is never deleted-from), recording each skipped\n * key as a non-fatal `SceneInstantiateDiagnostic` into the passed accumulator.\n * All known fields still write through, so one bad field cannot blank the\n * entity or the scene (C-AC-02/03/04).\n */\nexport function worldBuildSceneEntityComponentDatas(\n world: World,\n node: import('@forgeax/engine-types').SceneEntity,\n mapping: Uint32Array,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<ComponentData[], EcsError> {\n const out: ComponentData[] = [];\n const nodeLocalId = node.localId as unknown as number;\n for (const compName of Object.keys(node.components)) {\n const token = world.components.resolve(compName);\n if (token === undefined) {\n return err(new ComponentNotDefinedError(compName));\n }\n const raw = node.components[compName] ?? {};\n const schema = componentSchema(token) as Record<string, string>;\n const remappedRaw: Record<string, unknown> = {};\n for (const fieldName of Object.keys(raw)) {\n const fieldType = schema[fieldName];\n // C-R2: unknown key -> skip (do not copy into remappedRaw, do not\n // mutate the source `raw`) and record a structured diagnostic. The\n // downstream `spawn` only sees schema-valid keys, so its own\n // validateComponentDataKeys gate stays green.\n if (fieldType === undefined) {\n diagnostics.push({ component: compName, field: fieldName, localId: nodeLocalId });\n continue;\n }\n const value = (raw as Record<string, unknown>)[fieldName];\n const kind = classifyEntityField(token, fieldName);\n if (kind !== null) {\n // Entity / array<entity> field — remap through the shared kernel.\n // localId -> live Entity. Slots not yet spawned hold ENTITY_NULL_RAW.\n const sceneRemap = (localId: number): number => {\n if (localId < 0 || localId >= mapping.length) return ENTITY_NULL_RAW;\n const live = mapping[localId];\n return live === undefined || live === ENTITY_NULL_RAW ? ENTITY_NULL_RAW : live;\n };\n remappedRaw[fieldName] = remapEntityFieldValue(value, kind, sceneRemap);\n } else {\n remappedRaw[fieldName] = value;\n }\n }\n const filled = fillComponentDefaults(token, remappedRaw);\n out.push({ component: token, data: filled as never });\n }\n return ok(out);\n}\n/**\n * @internal feat-20260713 M2 / w8: apply one MountOverride to a live member\n * entity column. The `field?` shape is the add-or-patch discriminant:\n *\n * - `field` present -> PATCH one field: `world.set(member, comp, {[field]:\n * value})`. Omitted fields keep their authored / existing values.\n * - `field` absent -> ADD/UPSERT the whole component: `value` is the\n * per-field value map for `comp`. When the member already carries `comp`\n * it is upserted (set-over each supplied field + schema defaults for the\n * omitted ones — the whole component is rewritten from the value map +\n * defaults, never a `component-already-present` error). When absent it is\n * added fresh via `addComponent` (fillComponentDefaults fills omitted\n * fields). The value-map is fed through `fillComponentDefaults` so the\n * add and upsert paths write byte-identical rows.\n *\n * Component registration + value-key validation happened at\n * `_validateMountOverrides` (fail-fast before any spawn); by this point the\n * comp resolves through the World-local catalog and the value keys are schema-valid.\n * still guards defensively (an unregistered comp is a no-op skip, matching\n * the prior field-patch behaviour). Returns the underlying set / addComponent\n * Result so a shared-field value gate (D-4) or any other write error\n * propagates unchanged.\n */\nexport function worldApplyMountOverride(\n world: World,\n member: EntityHandle,\n ov: MountOverride,\n): Result<void, EcsError> {\n const ovToken = world.components.resolve(ov.comp);\n if (ovToken === undefined) return ok(undefined);\n if (ov.field !== undefined) {\n // PATCH one field.\n return world.set(member, ovToken, { [ov.field]: ov.value } as never);\n }\n // ADD/UPSERT the whole component. Fill omitted fields from the schema so\n // add and upsert produce identical rows (upsert = full rewrite from the\n // value map + defaults).\n const rawValue = (ov.value ?? {}) as Record<string, unknown>;\n const filled = fillComponentDefaults(ovToken as Component, rawValue);\n const has = world.get(member, ovToken);\n if (has.ok) {\n // Already present -> upsert (set every filled field, no duplicate error).\n return world.set(member, ovToken, filled as never);\n }\n return world.addComponent(member, { component: ovToken, data: filled as never });\n}\n/**\n * @internal R2/B-3 + R2/B-4: validate `mount.overrides[]` BEFORE any\n * spawn so a malformed override fails fast with no observable side\n * effects (charter P3 explicit-failure). Two checks:\n *\n * 1. `override.localId` must address a slot inside the parent-namespace\n * member window `[memberFirst, memberFirst + memberCount)` (AC-06).\n * 2. `override.field` must exist in the resolved component schema\n * (AC-07). When the component is unregistered we cannot validate the\n * field shape; let the existing fall-through path proceed (the\n * catalog guard inside the override-application loop\n * will skip the write).\n */\nexport function worldValidateMountOverrides(\n world: World,\n mount: SceneInstanceMount,\n): Result<void, EcsError> {\n const overrides = mount.overrides;\n if (overrides === undefined) return ok(undefined);\n const memberFirst = mount.memberFirst as unknown as number;\n const memberCount = mount.memberCount;\n const memberLast = memberFirst + memberCount;\n const mountLid = mount.localId as unknown as number;\n for (const ov of overrides) {\n const ovLid = ov.localId as unknown as number;\n // R2/B-3: parent-namespace check — override.localId must lie in the\n // member window [memberFirst, memberFirst + memberCount).\n if (ovLid < memberFirst || ovLid >= memberLast) {\n return err({\n code: 'pack-mount-override-localid-out-of-range' as PackErrorCode,\n expected: `override.localId in [${memberFirst}, ${memberLast})`,\n hint: PACK_ERROR_HINTS['pack-mount-override-localid-out-of-range'],\n detail: {\n code: 'pack-mount-override-localid-out-of-range',\n overrideLocalId: ovLid,\n mountLocalId: mountLid,\n memberCount,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n // feat-20260713 M2 / w8: double-branch schema check.\n // - field-patch form (field present): the component (when registered)\n // must declare `override.field` in its schema (R2/B-4, unchanged).\n // - component-add form (field absent): the component MUST be registered\n // (component-not-defined otherwise) AND every key in the value map\n // must be a schema field (pack-mount-override-unknown-field).\n const ovToken = world.components.resolve(ov.comp);\n if (ov.field !== undefined) {\n if (ovToken !== undefined) {\n const schema = componentSchema(ovToken) as Record<string, unknown>;\n if (!(ov.field in schema)) {\n return err({\n code: 'pack-mount-override-unknown-field' as PackErrorCode,\n expected: `override.field defined on component '${ov.comp}'`,\n hint: PACK_ERROR_HINTS['pack-mount-override-unknown-field'],\n detail: {\n code: 'pack-mount-override-unknown-field',\n comp: ov.comp,\n field: ov.field,\n mountLocalId: mountLid,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n } else {\n // component-add form: comp must be registered so we can validate + apply\n // the whole component (add/upsert needs the schema).\n if (ovToken === undefined) {\n return err(new ComponentNotDefinedError(ov.comp));\n }\n const schema = componentSchema(ovToken) as Record<string, unknown>;\n const valueMap = (ov.value ?? {}) as Record<string, unknown>;\n for (const key of Object.keys(valueMap)) {\n if (!(key in schema)) {\n return err({\n code: 'pack-mount-override-unknown-field' as PackErrorCode,\n expected: `override.value keys defined on component '${ov.comp}'`,\n hint: PACK_ERROR_HINTS['pack-mount-override-unknown-field'],\n detail: {\n code: 'pack-mount-override-unknown-field',\n comp: ov.comp,\n field: key,\n mountLocalId: mountLid,\n } as PackErrorDetail,\n } as unknown as EcsError);\n }\n }\n }\n }\n return ok(undefined);\n}\n/** @internal Spawn the mount-entity slot carrying mount.components (if any).\n *\n * R2/B-1: the mount entity is a structural intermediate in the ChildOf\n * chain `cube -> innerSyntheticRoot -> mountEntity -> outerSyntheticRoot`,\n * so it MUST carry Transform whenever Transform is registered (mirrors\n * the D-V-0 synthetic-root invariant). Otherwise propagateTransforms\n * walking the chain hits a Transform-less parent and emits per-frame\n * `RhiError(hierarchy-broken)` (verify R1 root cause of the\n * hello-scene-nesting demo black frames).\n */\nexport function worldSpawnMountEntity(\n world: World,\n mount: SceneInstanceMount,\n mapping: Uint32Array,\n diagnostics: SceneInstantiateDiagnostic[],\n): Result<EntityHandle, EcsError> {\n const fakeNode: import('@forgeax/engine-types').SceneEntity = {\n localId: mount.localId,\n components: mount.components ?? {},\n };\n const cdRes = worldBuildSceneEntityComponentDatas(world, fakeNode, mapping, diagnostics);\n if (!cdRes.ok) return cdRes;\n // R2/B-1: ensure Transform is attached so propagateTransforms can walk\n // through this entity. Layer-2 defaults supply identity TRS; the\n // mount.components overlay (when present and including Transform) takes\n // precedence and is already in cdRes.value.\n const transformToken = world.components.resolve('Transform');\n if (transformToken !== undefined) {\n const hasTransform = cdRes.value.some((c) => c.component === transformToken);\n if (!hasTransform) {\n cdRes.value.push({ component: transformToken, data: {} as never });\n }\n }\n if (cdRes.value.length === 0) {\n // Mount has no components AND Transform is unregistered (rare unit-\n // test path). Fall back to the placeholder ChildOf so the spawn has\n // a real archetype. Step 5 overwrites this placeholder.\n const childOfToken = world.components.resolve('ChildOf');\n if (childOfToken === undefined) {\n return err(new ComponentNotDefinedError('ChildOf'));\n }\n cdRes.value.push({\n component: childOfToken,\n data: { parent: ENTITY_NULL_RAW } as never,\n });\n }\n return (world.spawn as (...c: ComponentData[]) => Result<EntityHandle, EcsError>)(...cdRes.value);\n}\n/** @internal Resolve mount.source through the wired SceneAssetResolver. */\nexport function worldResolveMountSource(\n world: World,\n source: number | string,\n parentHandle: Handle<'SceneAsset', 'shared'>,\n): Result<Handle<'SceneAsset', 'shared'>, EcsError> {\n const resolver = worldGetSceneAssetResolver(world);\n if (resolver === null) {\n return err({\n code: 'stale-entity' as const,\n expected: 'wired SceneAssetResolver (auto-wired by engine.assets.instantiate)',\n hint:\n 'engine.assets.instantiate sugar wires this for you; ' +\n 'call worldSetSceneAssetResolver before nested scene expansion.',\n detail: { entity: 0, slot: 0, generation: 0 },\n } as unknown as EcsError);\n }\n const r = resolver(source, parentHandle);\n if (!r.ok) {\n // Resolver carries `unknown` err (loose contract — engine-runtime may\n // wire any shape); narrow back to EcsError here at the boundary.\n return err(r.error as EcsError);\n }\n return ok(r.value);\n}\n/** @internal Convert mount.overrides Map shape to the SceneInstanceState shape.\n *\n * feat-20260713 M1 / w4: `field` is optional (add-or-patch discriminant). In\n * M1 only the field-patch form reaches this builder (the component-add form\n * fails fast in the apply loops); the record type stays `field?: string` so\n * the M2 add path can flow through untouched. `exactOptionalPropertyTypes`\n * forbids writing an explicit `field: undefined`, so omit the key when absent.\n */\nexport function worldMountOverridesToStateMap(\n src: Map<LocalEntityId, Map<string, MountOverride>>,\n): Map<LocalEntityId, Map<string, { comp: string; field?: string; value: unknown }>> {\n const out = new Map<\n LocalEntityId,\n Map<string, { comp: string; field?: string; value: unknown }>\n >();\n for (const [lid, fields] of src) {\n const m = new Map<string, { comp: string; field?: string; value: unknown }>();\n for (const [k, v] of fields) {\n m.set(k, {\n comp: v.comp,\n value: v.value,\n ...(v.field !== undefined ? { field: v.field } : {}),\n });\n }\n out.set(lid, m);\n }\n return out;\n}\n/** @internal Set the payload of an already-allocated SceneInstance state ref. */\nexport function worldSetUniqueRefPayload<T>(\n world: World,\n handle: Handle<string, 'unique'>,\n payload: T,\n): void {\n sceneWorldState(world).statePayloads.set(Number(handle), payload);\n}\n\n/**\n * @internal Resolve the SceneInstanceState payload behind the\n * `SceneInstance.state` ref column on `root`. Returns Err when `root`\n * does not carry SceneInstance or the ref slot is dead.\n */\nexport function worldResolveSceneInstanceStatePayload(\n world: World,\n root: EntityHandle,\n): Result<SceneInstanceStatePayload, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const r = world.get(root, sceneInstanceToken);\n if (!r.ok) return r;\n const stateRefRaw = (r.value as unknown as { state: number }).state;\n const stateRefHandle = toUnique<'SceneInstanceState'>(stateRefRaw);\n const payload = sceneWorldState(world).statePayloads.get(Number(stateRefHandle));\n if (payload === undefined) {\n return err(\n new StaleEntityError(root as unknown as number, entityIndex(root), entityGeneration(root), {\n operation: 'resolveSceneInstanceState',\n component: 'SceneInstance',\n expectedGeneration: entityGeneration(root),\n actualGeneration: entityGeneration(root),\n }),\n );\n }\n return ok(payload as SceneInstanceStatePayload);\n}\n/**\n * Public sugar — get the SceneInstanceState payload (Map / Set view) for\n * `root`. Equivalent to `world.get(root, SceneInstance)` followed by a\n * managed-ref resolution; provided so AI users do not have to learn the\n * `ref<T>` slot resolution mechanic for the common read path.\n */\nexport function worldGetSceneInstanceState(\n world: World,\n root: EntityHandle,\n): Result<SceneInstanceStatePayload, EcsError> {\n return worldResolveSceneInstanceStatePayload(world, root);\n}\n/**\n * Despawn a SceneInstance root + all its members. `opts.keepDetached`\n * preserves members marked via `worldDetachSceneMember` (plan-strategy\n * §D-5). Returns the count of entities actually despawned (root + each\n * non-detached member).\n *\n * For a plain entity (no SceneInstance), behaviour matches\n * `world.despawn(entity)` followed by `despawnDescendants(entity)` — i.e.\n * `keepDetached` is a no-op.\n */\nexport function worldDespawnScene(\n world: World,\n root: EntityHandle,\n opts?: { keepDetached?: boolean },\n): Result<number, EcsError> {\n const dRes = worldDespawnDescendants(world, root, opts);\n if (!dRes.ok) return dRes;\n const drop = world.despawn(root);\n if (!drop.ok) return drop;\n return ok(dRes.value + 1);\n}\n/**\n * Despawn every descendant of `root` reachable through Children mirror /\n * SceneInstance.mapping. `opts.keepDetached` is honoured only when `root`\n * carries a SceneInstance (otherwise the option is ignored — there is no\n * detached set on a plain entity).\n *\n * Returns the count of entities despawned. The `root` itself is NOT\n * despawned (that is `despawnScene`'s extra step).\n */\nexport function worldDespawnDescendants(\n world: World,\n root: EntityHandle,\n opts?: { keepDetached?: boolean },\n): Result<number, EcsError> {\n let detached: Set<LocalEntityId> | null = null;\n let entityToLocalId: Map<EntityHandle, LocalEntityId> | null = null;\n if (opts?.keepDetached === true) {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (stateRes.ok) {\n detached = stateRes.value.detachedLocalIds;\n entityToLocalId = stateRes.value.entityToLocalId;\n }\n }\n let count = 0;\n // Collect descendants first (DFS via iterDescendants) to avoid mutating\n // while iterating. SceneInstance.mapping also owns members that may not be\n // reachable through a Children mirror in a partially registered host. The\n // nested anchor list closes that same ownership boundary for mounted scenes.\n const list: EntityHandle[] = [];\n const seen = new Set<number>();\n const collect = (anchor: EntityHandle): void => {\n for (const e of world.iterDescendants(anchor)) {\n const raw = e as unknown as number;\n if (!seen.has(raw)) {\n seen.add(raw);\n list.push(e);\n }\n }\n const stateRes = worldResolveSceneInstanceStatePayload(world, anchor);\n if (!stateRes.ok) return;\n for (const e of stateRes.value.entityToLocalId.keys()) {\n const raw = e as unknown as number;\n if (!seen.has(raw)) {\n seen.add(raw);\n list.push(e);\n }\n }\n for (const nestedRoot of stateRes.value.mountRoots) {\n const raw = nestedRoot as unknown as number;\n if (seen.has(raw)) continue;\n seen.add(raw);\n list.push(nestedRoot);\n collect(nestedRoot);\n }\n };\n collect(root);\n const childOfToken = world.components.resolve('ChildOf');\n // ChildOf uses linkedSpawn, so a parent-first pass would recursively retire\n // its children before this function can count them. Sort the ownership set\n // by its live ChildOf depth instead of relying on mirror traversal order;\n // nested SceneInstance roots are siblings of their mount carrier in the\n // flattened traversal but parents of the mounted members.\n const owned = new Set(list.map((entity) => Number(entity)));\n const ownedDepth = (entity: EntityHandle): number => {\n if (childOfToken === undefined) return 0;\n let current = entity;\n let depth = 0;\n const visited = new Set<number>();\n while (!visited.has(Number(current))) {\n visited.add(Number(current));\n const parentRes = world.get(current, childOfToken);\n if (!parentRes.ok) break;\n const parent = (parentRes.value as { parent: EntityHandle }).parent;\n if (!owned.has(Number(parent))) break;\n depth += 1;\n current = parent;\n }\n return depth;\n };\n list.sort((a, b) => ownedDepth(b) - ownedDepth(a));\n for (const e of list) {\n if (detached !== null) {\n const lid = entityToLocalId?.get(e);\n if (lid !== undefined && detached.has(lid)) {\n if (childOfToken !== undefined) {\n world.removeComponent(e, childOfToken);\n }\n continue;\n }\n }\n const r = world.despawn(e);\n if (!r.ok) {\n if (r.error.code === 'stale-entity') continue;\n return r;\n }\n count += 1;\n }\n return ok(count);\n}\n/**\n * Write a runtime override to a member entity belonging to `root`. Routes\n * through `world.set(member, comp, { [field]: value })` after an entity-\n * scope guard so cross-instance writes fail-fast. Type-mismatch surfaces\n * `EcsErrorCode = 'scene-override-type-mismatch'` (D-9).\n */\nexport function worldSetSceneOverride<S extends ComponentSchema>(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n component: Component<string, S>,\n field: keyof ShapeOf<S> & string,\n value: unknown,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) {\n return err(\n new StaleEntityError(\n member as unknown as number,\n entityIndex(member),\n entityGeneration(member),\n {\n operation: 'setSceneOverride',\n component: component.name,\n expectedGeneration: entityGeneration(member),\n actualGeneration: entityGeneration(member),\n },\n ),\n );\n }\n // Type guard: only check primitive scalar field types where we can\n // narrow `typeof`; ref / handle / entity / array / buffer fields skip\n // (write would surface a deeper error from set).\n const schemaType = (componentSchema(component) as Record<string, string>)[field];\n if (schemaType !== undefined && isPrimitiveScalarFieldType(schemaType)) {\n const expectJsType = primitiveJsType(schemaType);\n const actualJsType = typeof value;\n if (expectJsType !== actualJsType) {\n return err({\n code: 'scene-override-type-mismatch' as const,\n expected: `value typeof === ${expectJsType}`,\n hint:\n `setSceneOverride(${component.name}.${field}) expected ${expectJsType}, ` +\n `got ${actualJsType}; coerce or pick a different override path.`,\n detail: {\n code: 'scene-override-type-mismatch' as const,\n comp: component.name,\n field: field as string,\n expectedType: schemaType,\n actualType: actualJsType,\n },\n } as unknown as EcsError);\n }\n }\n const setRes = world.set(member, component, { [field]: value } as Partial<InputShapeOf<S>>);\n if (!setRes.ok) return setRes;\n // Record into state.overrides\n let fieldMap = state.overrides.get(lid);\n if (fieldMap === undefined) {\n fieldMap = new Map();\n state.overrides.set(lid, fieldMap);\n }\n fieldMap.set(`${component.name}:${field}`, {\n comp: component.name,\n field: field as string,\n value,\n });\n return ok(undefined);\n}\n/**\n * Drop a runtime override (and any mount-time override for the same\n * (member, comp, field) triple); roll the live column value back to the\n * source SceneAsset's layer-1 explicit value (M2 v1 — M3+ widens to layer\n * 2/3 defaults via fillComponentDefaults).\n */\nexport function worldRemoveSceneOverride<S extends ComponentSchema>(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n component: Component<string, S>,\n field: keyof ShapeOf<S> & string,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n const fieldMap = state.overrides.get(lid);\n if (fieldMap !== undefined) {\n fieldMap.delete(`${component.name}:${field}`);\n if (fieldMap.size === 0) state.overrides.delete(lid);\n }\n // Look up the source SceneAsset layer-1 value.\n const assetRes = worldResolveSceneAsset(world, state.source);\n if (!assetRes.ok) return assetRes;\n const node = assetRes.value.entities.find(\n (n) => (n.localId as unknown as number) === (lid as unknown as number),\n );\n const layer1 = node?.components[component.name] as Record<string, unknown> | undefined;\n if (layer1 !== undefined && field in layer1) {\n const r = world.set(member, component, { [field]: layer1[field] } as Partial<InputShapeOf<S>>);\n if (!r.ok) return r;\n }\n return ok(undefined);\n}\n/** Mark a member entity detached. Idempotent (set semantics). */\nexport function worldDetachSceneMember(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n): Result<void, EcsError> {\n const sceneInstanceToken = world.components.resolve('SceneInstance');\n if (sceneInstanceToken === undefined) {\n return err(new ComponentNotDefinedError('SceneInstance'));\n }\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n state.detachedLocalIds.add(lid);\n return ok(undefined);\n}\n/** Clear a detached mark. Idempotent (set semantics). */\nexport function worldReattachSceneMember(\n world: World,\n root: EntityHandle,\n member: EntityHandle,\n): Result<void, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n const state = stateRes.value;\n const lid = state.entityToLocalId.get(member);\n if (lid === undefined) return ok(undefined);\n state.detachedLocalIds.delete(lid);\n return ok(undefined);\n}\n/**\n * Get the SceneAsset handle a SceneInstance root was instantiated from.\n * Returns Err on a plain entity (no SceneInstance component).\n */\nexport function worldGetSceneAssetForInstance(\n world: World,\n root: EntityHandle,\n): Result<Handle<'SceneAsset', 'shared'>, EcsError> {\n const stateRes = worldResolveSceneInstanceStatePayload(world, root);\n if (!stateRes.ok) return stateRes;\n return ok(stateRes.value.source);\n}\n\n// SceneInstanceStatePayload — internal echo of the runtime\n// `SceneInstanceState` interface for ECS-side consumption (engine-ecs cannot\n// value-import engine-runtime by AC-29; structural shape only).\n// ────────────────────────────────────────────────────────────────────────────\n\n/** @internal Structural payload behind `SceneInstance.state` ref column. */\nexport interface SceneInstanceStatePayload {\n readonly source: Handle<'SceneAsset', 'shared'>;\n readonly entityToLocalId: Map<EntityHandle, LocalEntityId>;\n readonly detachedLocalIds: Set<LocalEntityId>;\n readonly overrides: Map<\n LocalEntityId,\n Map<string, { readonly comp: string; readonly field?: string; readonly value: unknown }>\n >;\n readonly rootEntities: EntityHandle[];\n /** Synthetic roots of recursively mounted SceneAssets owned by this instance. */\n readonly mountRoots: EntityHandle[];\n readonly totalSlots: number;\n readonly mountTimeOverrides: readonly MountOverride[];\n}\n\n/**\n * Topological sort over the implicit ChildOf graph (parents before children).\n * Cycle-free input always covers all n nodes; cyclic input emits whatever was\n * reachable from indegree-0 (the fallback caller handles cycle reporting via\n * `pack-cyclic-reference` at the upstream scanner / runtime path).\n */\nfunction sceneTopoSort(\n nodes: readonly import('@forgeax/engine-types').SceneEntity[],\n): readonly number[] {\n const n = nodes.length;\n const childrenOf: number[][] = Array.from({ length: n }, () => []);\n const indeg = new Uint32Array(n);\n const localIdToIdx = new Map<number, number>();\n for (let i = 0; i < n; i += 1) {\n const node = nodes[i];\n if (node === undefined) continue;\n localIdToIdx.set(node.localId as unknown as number, i);\n }\n for (let i = 0; i < n; i += 1) {\n const node = nodes[i];\n if (node === undefined) continue;\n const child = node.components.ChildOf;\n if (child === undefined) continue;\n const p = (child as Record<string, unknown>).parent;\n if (typeof p === 'number') {\n const parentIdx = localIdToIdx.get(p);\n if (parentIdx !== undefined && parentIdx !== i) {\n childrenOf[parentIdx]?.push(i);\n indeg[i] = (indeg[i] ?? 0) + 1;\n }\n }\n }\n const order: number[] = [];\n const queue: number[] = [];\n for (let i = 0; i < n; i += 1) if ((indeg[i] ?? 0) === 0) queue.push(i);\n while (queue.length > 0) {\n const head = queue.shift();\n if (head === undefined) break;\n order.push(head);\n for (const c of childrenOf[head] ?? []) {\n indeg[c] = (indeg[c] ?? 0) - 1;\n if ((indeg[c] ?? 0) === 0) queue.push(c);\n }\n }\n // Append any nodes left unvisited (defensive — cycle would surface here).\n for (let i = 0; i < n; i += 1) {\n if (!order.includes(i) && nodes[i] !== undefined) order.push(i);\n }\n return order;\n}\n\n/**\n * @internal feat-20260713 M2 / w8: SceneInstanceState map key for a\n * MountOverride. Field-patch form keys by `comp:field` (one entry per patched\n * field); component-add form keys by `comp` (one entry per added component). The\n * two key shapes cannot collide because a field-patch always carries a `:field`\n * suffix. Later array entries for the same key overwrite earlier ones, matching\n * the array-order apply semantics.\n */\nfunction mountOverrideStateKey(ov: MountOverride): string {\n return ov.field !== undefined ? `${ov.comp}:${ov.field}` : ov.comp;\n}\n\n/** Schema field types that are JS primitives (typeof checkable). */\nfunction isPrimitiveScalarFieldType(fieldType: string): boolean {\n if (\n fieldType === 'f32' ||\n fieldType === 'f64' ||\n fieldType === 'u32' ||\n fieldType === 'i32' ||\n fieldType === 'u8' ||\n fieldType === 'i8' ||\n fieldType === 'u16' ||\n fieldType === 'i16' ||\n fieldType === 'bool' ||\n fieldType === 'string'\n ) {\n return true;\n }\n if (fieldType.startsWith('enum<')) return true;\n return false;\n}\n\n/** Map a primitive scalar field type to the runtime `typeof` it should narrow to. */\nfunction primitiveJsType(fieldType: string): string {\n if (fieldType === 'bool') return 'boolean';\n if (fieldType === 'string') return 'string';\n return 'number';\n}\n","import type { EntityHandle } from '@forgeax/engine-ecs';\n\nexport type SceneErrorCode = 'hierarchy-broken' | 'hierarchy-cycle';\n\n/** Scene-instantiation failures owned by the scene package. */\nexport type SceneInstanceErrorCode = 'component-not-defined' | 'scene-override-type-mismatch';\n\nexport { ComponentNotDefinedError } from '@forgeax/engine-ecs/projection';\n\nexport interface SceneErrorDetail {\n readonly entity: EntityHandle;\n readonly parent: EntityHandle;\n}\n\nexport class SceneError extends Error {\n readonly code: SceneErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: SceneErrorDetail | undefined;\n\n constructor(args: {\n code: SceneErrorCode;\n expected: string;\n hint: string;\n detail?: SceneErrorDetail;\n }) {\n super(`[SceneError ${args.code}] expected: ${args.expected}; hint: ${args.hint}`);\n this.name = 'SceneError';\n this.code = args.code;\n this.expected = args.expected;\n this.hint = args.hint;\n this.detail = args.detail;\n }\n}\n","// @forgeax/engine-runtime - Children (forward-list of child entities).\n//\n// Schema: 1 array<entity> field `entities` (variable-length, ECS-managed via\n// the BufferPool slot column + sidecar count column allocated by the ECS M2\n// `world.push` / `world.pop` / `world.capacity` command surface).\n//\n// feat-20260515-buffer-array-vocab-collapse M3 / w17:\n// the legacy `VarArrayView<Entity>` value-shape wrapper was retired in\n// favour of a direct `TypedArray` snapshot returned by `world.get` plus the\n// three `world` commands. AI users mutate the list through:\n//\n// world.push(parent, Children, 'entities', child).unwrap();\n// world.pop(parent, Children, 'entities').unwrap();\n// world.capacity(parent, Children, 'entities').unwrap();\n//\n// And read through the read-only `Uint32Array` snapshot:\n//\n// const snap = world.get(parent, Children).unwrap().entities;\n// const liveCount = snap.length;\n// for (let i = 0; i < liveCount; i++) { const child = snap[i]; ... }\n//\n// Snapshot length equals the live element count (sidecar count column owned\n// by the ECS layer); the snapshot is rematerialised on every `world.get`\n// (D-4 no-cache); writes routed through the snapshot are undefined behaviour\n// (the contract is read-only, plan-strategy §2.2 D-R3).\n//\n// feat-20260531-ecs-relationship-abstraction-bidirectional-sync M4 / t20:\n// Children is the MIRROR side of the ChildOf relationship. Its schema is\n// unchanged (the `entities: 'array<entity>'` shape is exactly what the\n// relationship mirror contract requires), but the engine now maintains this\n// list automatically whenever ChildOf is added / removed / reparented on a\n// child entity (M2 bidirectional-sync hook on ChildOf). The prior OOS-10\n// \"AI users keep the two sides consistent themselves\" contract is retired:\n// `world.addComponent(child, ChildOf{parent})` appends `child` to\n// `parent.Children.entities`, `world.removeComponent` / reparent prunes it.\n// AI users still MAY push/pop the list manually (the three `world` commands\n// below remain valid for non-ChildOf forward-lists), but for the ChildOf\n// hierarchy the engine owns consistency.\n//\n// feat-20260514-ecs-children-instances-managed-buffer-array M3 / w13 (kept\n// for context): migrated from the legacy `{ count: 'u32' }` advisory marker\n// to the real variable-length entity-array storage path.\n// - OOS-09 (prior loop): no `addChild` / `removeChild` / `removeChildren`\n// Commands API. Retired this feat: `world.addChild` / `world.removeChild`\n// / `world.reparent` ship in M3, plus the relationship hook above.\n// - OOS-01 (prior loop): no dangling-entity sweep on `array<entity>`. If a\n// child entity has been despawned, the stored u32 still occupies the\n// slot; AI users explicitly call the engine's entity-liveness check\n// before consuming a child id (see `world.get(child, ChildOf)` returns\n// `Result.err(...)` for a despawned entity --- the stored u32 surfaces\n// as a dead handle, not a silent zero). Charter proposition 4\n// (explicit failure): the engine does not silently drop dangling\n// entries.\n//\n// charter mapping: proposition 2 (Bevy ChildOf+Children pair, holder\n// perspective) + proposition 3 (machine-readable schema:\n// `componentSchema(Children).entities === 'array<entity>'`) + proposition 4 (explicit\n// failure: dangling entries surface to the AI user via `world.get(parent, Entity)` liveness probe,\n// not silent drop) + proposition 5 (consistent abstraction: Children is the\n// generic relationship-mirror shape, not a ChildOf special case).\n\nimport { defineRelationship } from '@forgeax/engine-ecs';\n\n/**\n * Hierarchy forward-list of child entities.\n *\n * `entities` is a variable-length `array<entity>` field; each element is\n * an `Entity` u32 the AI user pushed via\n * `world.push(parent, Children, 'entities', child)`. The value returned by\n * `world.get(parent, Children).unwrap().entities` is a read-only\n * `Uint32Array` snapshot rematerialised fresh on every read (D-4 no-cache);\n * the snapshot's `length` equals the live element count.\n *\n * Invariants:\n * - Children is NOT consumed by `propagateTransforms` (which walks ChildOf\n * upward, D-P2). The forward list is for AI-user traversal / debug /\n * inspection.\n * - Children <-> ChildOf consistency is maintained by the engine via the\n * ChildOf `relationship` mirror hook (see ./child-of.ts): adding /\n * removing / reparenting ChildOf on a child auto-updates the parent's\n * `entities` list. AI users do not hand-sync the two sides for the\n * hierarchy.\n * - Stored entity u32s are NOT auto-cleared when the referenced entity is\n * despawned (OOS-01 above); a `world.get(despawnedChild, ...)` call\n * returns `Result.err(...)` so AI users discover the dangling state\n * through the engine's structured-error channel.\n *\n * @example Spawn a parent and two children via ChildOf (engine maintains Children):\n * const parent = world.spawn({ component: Transform, data: identityXf() }).unwrap();\n * const a = world.spawn(\n * { component: Transform, data: identityXf() },\n * { component: ChildOf, data: { parent } },\n * ).unwrap();\n * const b = world.spawn(\n * { component: Transform, data: identityXf() },\n * { component: ChildOf, data: { parent } },\n * ).unwrap();\n * // Read back via the read-only snapshot - engine appended a, b:\n * const snap = world.get(parent, Children).unwrap().entities;\n * for (let i = 0; i < snap.length; i++) {\n * const child = snap[i];\n * // ... consume; world.get(child, ...) surfaces a structured error\n * // if the child has been despawned (OOS-01 dangling-entity surface).\n * }\n */\nexport const { source: ChildOf, target: Children } = defineRelationship({\n sourceName: 'ChildOf',\n sourceField: 'parent',\n targetName: 'Children',\n targetField: 'entities',\n exclusive: true,\n linkedSpawn: true,\n});\n","import { defineComponent } from '@forgeax/engine-ecs';\n\n/** Per-entity morph weights; length is validated against the mesh target count. */\nexport const MorphWeights = defineComponent('MorphWeights', {\n weights: { type: 'array<f32>' },\n});\n","// @forgeax/engine-runtime --- Name component (built-in identifier).\n//\n// Single-field minimal skeleton: { value: 'string' }. Bare 'string' schema\n// vocab keyword routes through ECS UniqueRefStore (D-R3 single-arm managed\n// dispatch); the read shape is a native JS string.\n//\n// Lives in `runtime` rather than `ecs` because Name is a built-in *component*,\n// not part of the ECS framework itself (it does not participate in archetype /\n// query / world mechanics like the essential `Entity` component does). Mirrors\n// Bevy's split: `Entity` lives in `bevy_ecs`; `Name` lives in `bevy_core`.\n//\n// Migrated from packages/ecs/src/name.ts by tweak-20260612-ecs-concept-compression\n// (architecture-principles.md §1 SSOT: Name's authoritative location is the\n// runtime built-in components surface, not the ECS framework barrel).\n//\n// Naming follows the Bevy-aligned convention locked by feat-20260513:\n// single-semantic component drops the 'Component' suffix (Transform / Camera\n// / DirectionalLight / Name).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\nexport const Name = defineComponent('Name', { value: { type: 'string' } });\n","// @forgeax/engine-runtime - Transform (local TRS + world mat4).\n//\n// Schema: three local array<f32, N> columns -- pos (3-vec position), quat\n// (4-quat rotation, component order [x, y, z, w]), scale (3-vec scale) --\n// plus one `world: array<f32, 16>` field carrying the resolved world-space\n// mat4 (column-major 16 floats, written by the propagate kernel each frame).\n// Inline stride-N array columns (feat-20260602) store each row's N floats\n// contiguously, so per-row xyz locality is native to the column layout; the\n// former per-axis scalar decomposition (10 f32 columns) predates\n// inline array columns and was retired in feat-20260709 M2.\n//\n// The world column is the SSOT for the resolved world transform: a root's\n// world equals its local mat4; a child's world equals parent.world x local.\n// AI users author the local TRS arrays and read the derived world mat4 via\n// the ECS column-level array view (`world.get(e, Transform).world` -> live\n// Float32Array of 16 column-major floats). They MUST NOT hand-write the\n// world column -- it is overwritten by propagate.\n//\n// charter mapping: P1 (progressive disclosure via defaults map -- AI users\n// spawn with data: {} and get identity local TRS + identity world mat4),\n// F1 (context-limited: single-import barrel; 3 array keys replace 10 scalar\n// keys at every spawn call-site), P4 (consistent abstraction: pos / quat /\n// scale array columns follow the same access pattern as `world` -- learn\n// the flat column form once, apply it to every array<f32, N> field),\n// P3 (machine-readable schema > prose).\n\nimport { defineComponent } from '@forgeax/engine-ecs';\n\n// Column-major identity mat4 (16 floats) used as the world-column default so\n// `spawn({ component: Transform, data: {} })` lands an identity world view\n// before the first propagate pass writes the resolved transform.\nconst IDENTITY_MAT4 = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);\n\n/**\n * Transform: local position `pos` (xyz), rotation `quat` (quaternion,\n * component order [x, y, z, w]), scale `scale` (xyz), plus the resolved\n * `world` mat4 (column-major 16 floats).\n *\n * Local TRS is stored as three inline stride-N array<f32, N> columns. The\n * propagate kernel composes each entity's local TRS into a mat4 and writes it\n * into the `world` column (root: world = local; child: world = parent.world x\n * local) using `@forgeax/engine-math` mat4 / vec3 / quat APIs (charter P4: do\n * not reinvent math). MVP recomposes every frame; dirty-flag optimization is\n * owned by feat-future-render-world.\n *\n * The `world` column is a fixed-capacity `array<f32, 16>` (feat-20260602):\n * the 16 contiguous floats live inline in a stride-16 column -- no BufferPool\n * slot. Read it via `world.get(e, Transform).world` which returns a live\n * `Float32Array` aliasing the column buffer. The view is transient: it aliases\n * the archetype column buffer and is valid only until the next structural\n * change (spawn / despawn / addComponent / removeComponent). Re-fetch the view\n * on every access; holding a view across a structural change is undefined\n * behaviour (the backing `ArrayBuffer` is detached on column growth, and\n * swap-remove at the same row index points to the wrong entity). All existing\n * hot paths (propagate / render-extract / pick) already comply -- see\n * `packages/ecs/README.md` Transient view contract section.\n *\n * All three local columns carry explicit layer-2 defaults (identity\n * transform): `pos: [0, 0, 0]`, `quat: [0, 0, 0, 1]` (identity quaternion,\n * [x, y, z, w]), `scale: [1, 1, 1]`. quat and scale MUST stay explicit: the\n * layer-3 fallback for array<f32, N> is all-zero, which would land an invalid\n * zero quaternion / zero scale. The `world` field defaults to the identity\n * mat4. AI users spawn with `data: {}` or with only the fields they need to\n * override.\n *\n * @example Minimal spawn (local defaulted to identity, world = identity mat4):\n * world.spawn({ component: Transform, data: {} });\n *\n * @example Override only the position, leaving rotation/scale at identity:\n * world.spawn({ component: Transform, data: { pos: [0, 6, 0] } });\n *\n * @example Full explicit local form (defaults are opt-in):\n * world.spawn({ component: Transform, data: {\n * pos: [1, 2, 3],\n * quat: [0, 0, 0, 1], // [x, y, z, w]\n * scale: [1, 1, 1],\n * } });\n */\nexport const Transform = defineComponent('Transform', {\n pos: { type: 'array<f32, 3>', default: new Float32Array([0, 0, 0]) },\n // Component order [x, y, z, w] end to end (glTF-aligned; E6).\n quat: { type: 'array<f32, 4>', default: new Float32Array([0, 0, 0, 1]) },\n scale: { type: 'array<f32, 3>', default: new Float32Array([1, 1, 1]) },\n // `world` is field-level transient (D-5): scene collect skips it. The resolved\n // world mat4 is derived by the propagate kernel from the persisted local TRS\n // each frame, so serializing it would store reconstructable data (SSOT: local\n // TRS). Round-trip re-derives an equivalent world on the first propagate pass.\n world: { type: 'array<f32, 16>', default: IDENTITY_MAT4, transient: true },\n});\n","/** Immutable Scene collection policy shared by runtime collectors. */\nexport interface SceneCollectProfile {\n readonly includeComponent: (componentName: string, transient: boolean) => boolean;\n readonly includeField: (componentName: string, fieldName: string, transient: boolean) => boolean;\n}\n\nexport const SCENE_COLLECT_PROFILE: SceneCollectProfile = Object.freeze({\n includeComponent: (_componentName: string, transient: boolean) => !transient,\n includeField: (_componentName: string, _fieldName: string, transient: boolean) => !transient,\n});\n","import type { AssetRef, MountOverride, SceneAsset } from '@forgeax/engine-types';\nimport { err, ok, type Result } from '@forgeax/engine-types';\n\nexport type SceneComponentSchemaResolver = (\n componentName: string,\n) => Readonly<Record<string, string>> | undefined;\n\nexport interface SceneExternalizationError {\n readonly field: string;\n readonly value: unknown;\n}\n\nexport interface ExternalizedSceneAsset {\n readonly payload: Record<string, unknown>;\n readonly refs: readonly AssetRef[];\n}\n\nfunction sharedKind(type: string | undefined): 'one' | 'many' | undefined {\n if (type?.startsWith('shared<')) return 'one';\n if (type?.startsWith('array<shared<')) return 'many';\n return undefined;\n}\n\nfunction overrideGuids(\n override: MountOverride,\n resolveSchema: SceneComponentSchemaResolver,\n): readonly { field: string; guid: string }[] {\n const schema = resolveSchema(override.comp);\n const values =\n override.field !== undefined\n ? [[override.field, override.value] as const]\n : override.value !== null &&\n typeof override.value === 'object' &&\n !Array.isArray(override.value)\n ? Object.entries(override.value as Record<string, unknown>)\n : [];\n return values.flatMap(([field, value]) => {\n const kind = sharedKind(schema?.[field]);\n if (kind === 'one' && typeof value === 'string') return [{ field, guid: value }];\n if (kind === 'many' && Array.isArray(value)) {\n return value.flatMap((item) => (typeof item === 'string' ? [{ field, guid: item }] : []));\n }\n return [];\n });\n}\n\n/** Project a SceneAsset's shared asset fields into a payload plus indexed refs. */\nexport function externalizeSceneAsset(\n scene: SceneAsset,\n resolveSchema: SceneComponentSchemaResolver,\n): Result<ExternalizedSceneAsset, SceneExternalizationError> {\n const refs: AssetRef[] = [];\n const indexByGuid = new Map<string, number>();\n const addRef = (\n guid: string,\n sourceField: NonNullable<AssetRef['sourceField']>,\n sceneEntityId?: number,\n ): number => {\n const prior = indexByGuid.get(guid);\n if (prior !== undefined) return prior;\n const index = refs.length;\n refs.push({ guid, sourceField, ...(sceneEntityId === undefined ? {} : { sceneEntityId }) });\n indexByGuid.set(guid, index);\n return index;\n };\n\n const entities = scene.entities.map((entity) => {\n const components: Record<string, Record<string, unknown>> = {};\n for (const componentName of Object.keys(entity.components)) {\n const schema = resolveSchema(componentName);\n const source = entity.components[componentName] as Record<string, unknown> | undefined;\n if (source === undefined) continue;\n const fields: Record<string, unknown> = {};\n for (const fieldName of Object.keys(source)) {\n const value = source[fieldName];\n if (value === undefined) continue;\n const kind = sharedKind(schema?.[fieldName]);\n if (kind === 'one' && typeof value === 'string') {\n fields[fieldName] = addRef(value, { componentName, fieldName }, entity.localId as number);\n } else if (kind === 'many' && Array.isArray(value)) {\n fields[fieldName] = value.map((item, arrayIndex) =>\n typeof item === 'string'\n ? addRef(item, { componentName, fieldName, arrayIndex }, entity.localId as number)\n : item,\n );\n } else {\n fields[fieldName] = value;\n }\n }\n if (Object.keys(fields).length > 0 || Object.keys(schema ?? {}).length === 0) {\n components[componentName] = fields;\n }\n }\n return { localId: entity.localId as number, components };\n });\n\n const mounts = scene.mounts?.map((mount) => {\n const source =\n typeof mount.source === 'string'\n ? addRef(\n mount.source,\n { componentName: 'SceneInstance', fieldName: 'source' },\n mount.localId as number,\n )\n : (mount.source as number);\n for (const { field, guid } of (mount.overrides ?? []).flatMap((override) =>\n overrideGuids(override, resolveSchema),\n )) {\n addRef(guid, { componentName: 'SceneInstance', fieldName: `overrides.${field}` });\n }\n return {\n localId: mount.localId as number,\n source,\n memberFirst: mount.memberFirst as number,\n memberCount: mount.memberCount,\n ...(mount.parent === undefined ? {} : { parent: mount.parent as number }),\n ...(mount.publicationFence === undefined ? {} : { publicationFence: mount.publicationFence }),\n ...(mount.overrides === undefined\n ? {}\n : { overrides: mount.overrides.map((item) => ({ ...item })) }),\n };\n });\n for (const [arrayIndex, guid] of (scene.skinGuids ?? []).entries()) {\n if (typeof guid !== 'string') return err({ field: 'skinGuids', value: guid });\n addRef(guid, { componentName: '<scene>', fieldName: 'skinGuids', arrayIndex });\n }\n return ok({\n payload: {\n entities,\n ...(mounts === undefined || mounts.length === 0 ? {} : { mounts }),\n ...(scene.skinGuids === undefined\n ? {}\n : { skinGuids: scene.skinGuids.map((guid) => indexByGuid.get(guid) as number) }),\n },\n refs,\n });\n}\n","import {\n defineSystem,\n defineSystemSet,\n type EntityHandle,\n FixedUpdate,\n type SystemHandle,\n Update,\n type World,\n} from '@forgeax/engine-ecs';\nimport {\n createWorldProjection,\n setDerivedComponent,\n type WorldProjection,\n} from '@forgeax/engine-ecs/projection';\nimport { type Mat4, mat4 } from '@forgeax/engine-math';\nimport { err, ok, type Result } from '@forgeax/engine-types';\nimport { ChildOf } from '../components/child-of';\nimport { Transform } from '../components/transform';\nimport { SceneError } from '../errors';\nimport { projectHierarchy, type SceneHierarchySnapshot } from './hierarchy-projection';\n\nexport const PROPAGATE_TRANSFORMS_SYSTEM = 'propagateTransforms' as const;\nexport const PROPAGATE_TRANSFORMS_FIXED_SYSTEM = 'propagateTransformsFixed' as const;\nexport const TransformSet = defineSystemSet({ name: 'transform' });\nexport const TransformFixedSet = defineSystemSet({ name: 'transform-fixed' });\n\ninterface LocalState {\n readonly pos: Float32Array;\n readonly quat: Float32Array;\n readonly scale: Float32Array;\n}\n\ninterface PropagationCache {\n readonly projection: WorldProjection;\n readonly hierarchy: SceneHierarchySnapshot;\n readonly parentOf: ReadonlyMap<EntityHandle, EntityHandle>;\n readonly childrenOf: ReadonlyMap<EntityHandle, readonly EntityHandle[]>;\n readonly entities: ReadonlySet<EntityHandle>;\n readonly locals: Map<EntityHandle, LocalState>;\n readonly derived: Map<EntityHandle, Float32Array>;\n result: Result<void, SceneError>;\n}\n\nconst CACHE = new WeakMap<World, PropagationCache>();\n\ninterface TransformRegistrationLease {\n refs: number;\n}\n\n// A World can be consumed by more than one renderer/view. Keep the schedule\n// registration leased per World so one owner disposing cannot remove the\n// shared transform system from another owner. This is lifecycle bookkeeping,\n// not a second component/system authority; the World schedule remains the\n// source of truth.\nconst REGISTRATION_LEASES = new WeakMap<World, TransformRegistrationLease>();\n\nfunction copyState(value: {\n readonly pos: ArrayLike<number>;\n readonly quat: ArrayLike<number>;\n readonly scale: ArrayLike<number>;\n}): LocalState {\n return {\n pos: new Float32Array(value.pos),\n quat: new Float32Array(value.quat),\n scale: new Float32Array(value.scale),\n };\n}\n\nfunction sameArray(left: ArrayLike<number>, right: ArrayLike<number>): boolean {\n if (left.length !== right.length) return false;\n for (let index = 0; index < left.length; index += 1) {\n if (left[index] !== right[index]) return false;\n }\n return true;\n}\n\nfunction sameState(left: LocalState | undefined, right: LocalState): boolean {\n return (\n left !== undefined &&\n sameArray(left.pos, right.pos) &&\n sameArray(left.quat, right.quat) &&\n sameArray(left.scale, right.scale)\n );\n}\n\nfunction compose(state: LocalState, out: Mat4): void {\n mat4.compose(out, state.pos, state.quat, state.scale);\n}\n\nfunction indexChildren(\n hierarchy: SceneHierarchySnapshot,\n): ReadonlyMap<EntityHandle, readonly EntityHandle[]> {\n const childrenOf = new Map<EntityHandle, EntityHandle[]>();\n for (const [child, parent] of hierarchy.parentOf) {\n const children = childrenOf.get(parent);\n if (children === undefined) childrenOf.set(parent, [child]);\n else children.push(child);\n }\n return childrenOf;\n}\n\nfunction descendants(\n childrenOf: ReadonlyMap<EntityHandle, readonly EntityHandle[]>,\n roots: ReadonlySet<EntityHandle>,\n): Set<EntityHandle> {\n const affected = new Set(roots);\n const pending = [...roots];\n for (let index = 0; index < pending.length; index += 1) {\n const parent = pending[index];\n if (parent === undefined) continue;\n for (const child of childrenOf.get(parent) ?? []) {\n if (affected.has(child)) continue;\n affected.add(child);\n pending.push(child);\n }\n }\n return affected;\n}\n\nfunction hierarchyError(hierarchy: SceneHierarchySnapshot): Result<void, SceneError> {\n const first = hierarchy.diagnostics[0];\n if (first === undefined) return ok(undefined);\n return err(\n new SceneError({\n code: first.code,\n expected: first.expected,\n hint: first.hint,\n detail: first.detail,\n }),\n );\n}\n\nfunction buildCache(world: World, projection: WorldProjection): PropagationCache {\n const hierarchy = projectHierarchy(world);\n const locals = new Map<EntityHandle, LocalState>();\n const entities = new Set<EntityHandle>();\n const query = world.query({ read: [Transform], optional: [ChildOf] });\n if (query.ok) {\n for (const row of query.value) {\n const transform = row.get(Transform);\n if (transform === undefined) continue;\n entities.add(row.entity);\n locals.set(row.entity, copyState(transform));\n }\n }\n return {\n projection,\n hierarchy,\n parentOf: hierarchy.parentOf,\n childrenOf: indexChildren(hierarchy),\n entities,\n locals,\n derived: new Map(),\n result: ok(undefined),\n };\n}\n\nfunction deriveEntity(\n world: World,\n entity: EntityHandle,\n cache: PropagationCache,\n affected: ReadonlySet<EntityHandle>,\n visiting: Set<EntityHandle>,\n published: Set<EntityHandle>,\n): Result<void, SceneError> {\n if (!affected.has(entity) || cache.derived.has(entity)) return ok(undefined);\n if (visiting.has(entity)) return ok(undefined);\n visiting.add(entity);\n const local = cache.locals.get(entity);\n if (local === undefined) {\n visiting.delete(entity);\n return ok(undefined);\n }\n const parent = cache.parentOf.get(entity);\n if (parent !== undefined && cache.entities.has(parent)) {\n const parentResult = deriveEntity(world, parent, cache, affected, visiting, published);\n if (!parentResult.ok) return parentResult;\n }\n const localWorld = mat4.create();\n compose(local, localWorld);\n const parentWorld = parent === undefined ? undefined : cache.derived.get(parent);\n const resolved = mat4.create();\n if (parentWorld === undefined) resolved.set(localWorld);\n else mat4.multiply(resolved, parentWorld, localWorld);\n const write = setDerivedComponent(world, entity, Transform, { world: resolved });\n if (!write.ok) {\n visiting.delete(entity);\n return err(\n new SceneError({\n code: 'hierarchy-broken',\n expected: 'the derived Transform.world write to succeed',\n hint: 'inspect the ECS mutation error before retrying transform propagation',\n detail: { entity, parent: entity },\n }),\n );\n }\n cache.derived.set(entity, resolved);\n published.add(entity);\n visiting.delete(entity);\n return ok(undefined);\n}\n\nfunction drainPublishedChanges(\n cache: PropagationCache,\n published: ReadonlySet<EntityHandle>,\n): boolean {\n const drained = cache.projection.poll();\n if (drained.status === 'rebuild') return true;\n return drained.changes.every(\n (change) =>\n change.kind === 'derived-component-changed' &&\n change.component === Transform &&\n published.has(change.entity),\n );\n}\n\nexport function propagateTransforms(\n world: World,\n _hierarchy?: SceneHierarchySnapshot,\n): Result<void, SceneError> {\n let cache = CACHE.get(world);\n if (cache === undefined) {\n cache = buildCache(world, createWorldProjection(world, { components: [Transform, ChildOf] }));\n CACHE.set(world, cache);\n const initial = new Set(cache.entities);\n const published = new Set<EntityHandle>();\n for (const entity of initial) {\n const result = deriveEntity(world, entity, cache, initial, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n }\n\n const evidence = cache.projection.poll();\n if (evidence.status === 'rebuild') {\n cache = buildCache(world, cache.projection);\n CACHE.set(world, cache);\n const all = new Set(cache.entities);\n const published = new Set<EntityHandle>();\n for (const entity of all) {\n const result = deriveEntity(world, entity, cache, all, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n }\n if (evidence.changes.length === 0) return cache.result;\n\n const transformSeeds = new Set<EntityHandle>();\n let rebuild = false;\n for (const change of evidence.changes) {\n if (\n change.kind === 'entity-removed' ||\n change.kind === 'component-added' ||\n change.kind === 'component-removed'\n ) {\n rebuild = true;\n break;\n }\n if (change.kind !== 'component-changed') continue;\n if (change.component === ChildOf) {\n rebuild = true;\n break;\n }\n // Derived Transform publications are deliberately not dirty seeds. Only\n // authored component-changed records can invalidate local TRS state.\n if (change.component === Transform) transformSeeds.add(change.entity);\n }\n if (rebuild) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n\n // A World can record several authored writes before this pass. Read each\n // seed once so the final local state decides whether propagation is needed.\n const changed = new Set<EntityHandle>();\n for (const entity of transformSeeds) {\n const current = world.get(entity, Transform);\n if (!current.ok) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n const next = copyState(current.value);\n if (!sameState(cache.locals.get(entity), next)) changed.add(entity);\n cache.locals.set(entity, next);\n }\n if (changed.size === 0) return cache.result;\n\n const affected = descendants(cache.childrenOf, changed);\n const published = new Set<EntityHandle>();\n for (const entity of affected) cache.derived.delete(entity);\n for (const entity of affected) {\n const result = deriveEntity(world, entity, cache, affected, new Set(), published);\n if (!result.ok) return result;\n }\n if (!drainPublishedChanges(cache, published)) {\n CACHE.delete(world);\n return propagateTransforms(world);\n }\n cache.result = hierarchyError(cache.hierarchy);\n return cache.result;\n}\n\nexport const PropagateTransforms: SystemHandle<readonly []> = defineSystem({\n name: PROPAGATE_TRANSFORMS_SYSTEM,\n queries: [],\n fn: (world) => {\n const result = propagateTransforms(world);\n if (!result.ok) throw result.error;\n },\n});\n\nexport const PropagateTransformsFixed: SystemHandle<readonly []> = defineSystem({\n name: PROPAGATE_TRANSFORMS_FIXED_SYSTEM,\n queries: [],\n fn: PropagateTransforms.fn,\n});\n\nexport function registerPropagateTransforms(\n world: World,\n options: { beforeSystemName?: string } = {},\n): () => void {\n const existing = REGISTRATION_LEASES.get(world);\n if (existing !== undefined) {\n existing.refs += 1;\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n existing.refs -= 1;\n if (existing.refs === 0) {\n world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);\n world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);\n REGISTRATION_LEASES.delete(world);\n CACHE.delete(world);\n }\n };\n }\n if (options.beforeSystemName === undefined) {\n world.addSystems(Update, TransformSet, [PropagateTransforms]).unwrap();\n } else {\n world\n .addSystems(Update, TransformSet, [\n {\n name: PROPAGATE_TRANSFORMS_SYSTEM,\n queries: [],\n fn: PropagateTransforms.fn,\n before: [options.beforeSystemName],\n },\n ])\n .unwrap();\n }\n world.addSystems(FixedUpdate, TransformFixedSet, [PropagateTransformsFixed]).unwrap();\n const lease: TransformRegistrationLease = { refs: 1 };\n REGISTRATION_LEASES.set(world, lease);\n let active = true;\n return () => {\n if (!active) return;\n active = false;\n lease.refs -= 1;\n if (lease.refs !== 0) return;\n world.removeSystem(FixedUpdate, PROPAGATE_TRANSFORMS_FIXED_SYSTEM);\n world.removeSystem(Update, PROPAGATE_TRANSFORMS_SYSTEM);\n REGISTRATION_LEASES.delete(world);\n CACHE.delete(world);\n };\n}\n","import { Entity, type EntityHandle, type World } from '@forgeax/engine-ecs';\nimport { createWorldProjection } from '@forgeax/engine-ecs/projection';\nimport { ChildOf } from '../components/child-of';\nimport type { SceneErrorCode, SceneErrorDetail } from '../errors';\n\nexport interface SceneHierarchyDiagnostic {\n readonly code: SceneErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: SceneErrorDetail;\n}\n\nexport interface SceneHierarchySnapshot {\n readonly parentOf: ReadonlyMap<EntityHandle, EntityHandle>;\n readonly diagnostics: readonly SceneHierarchyDiagnostic[];\n getParent(entity: EntityHandle): EntityHandle | undefined;\n}\n\ninterface HierarchyProjectionCacheEntry {\n readonly changes: ReturnType<typeof createWorldProjection>;\n readonly snapshot: SceneHierarchySnapshot;\n}\n\n// A World can be observed by the transform system, renderer visibility, and\n// editor projections in the same frame. Keep one World-local projection so\n// those consumers do not each rescan every archetype. Structure changes and\n// the ChildOf component's own mutation token are the invalidation keys; the\n// global mutation epoch is intentionally too broad because animation and\n// runtime-only component writes may advance it every frame. Direct table\n// writes are internal-only and must not mutate authored hierarchy state.\nconst HIERARCHY_PROJECTION_CACHE = new WeakMap<World, HierarchyProjectionCacheEntry>();\n\nfunction diagnostic(\n code: SceneErrorCode,\n entity: EntityHandle,\n parent: EntityHandle,\n): SceneHierarchyDiagnostic {\n if (code === 'hierarchy-cycle') {\n return {\n code,\n expected: 'ChildOf parent edges form an acyclic live hierarchy',\n hint: 'remove one ChildOf edge from the reported cycle, then re-run the extract',\n detail: { entity, parent },\n };\n }\n return {\n code,\n expected: 'ChildOf.parent references a live entity in the same World',\n hint: 'remove the stale ChildOf component or restore the referenced parent in this World',\n detail: { entity, parent },\n };\n}\n\n/** Build the only World-local projection of ChildOf parent facts. */\nexport function projectHierarchy(world: World): SceneHierarchySnapshot {\n const cached = HIERARCHY_PROJECTION_CACHE.get(world);\n const changes = cached?.changes ?? createWorldProjection(world, { components: [ChildOf] });\n if (cached !== undefined) {\n const evidence = changes.poll();\n if (evidence.status === 'delta' && evidence.changes.length === 0) return cached.snapshot;\n }\n const liveEntities = new Set<EntityHandle>();\n const authoredParents = new Map<EntityHandle, EntityHandle>();\n\n const query = world.query({ read: [Entity], optional: [ChildOf] });\n if (query.ok) {\n for (const row of query.value) {\n liveEntities.add(row.entity);\n const parent = row.get(ChildOf)?.parent;\n if (parent !== undefined && parent !== null) authoredParents.set(row.entity, parent);\n }\n }\n\n const parentOf = new Map<EntityHandle, EntityHandle>();\n const diagnostics: SceneHierarchyDiagnostic[] = [];\n for (const [entity, parent] of authoredParents) {\n if (liveEntities.has(parent)) {\n parentOf.set(entity, parent);\n } else {\n diagnostics.push(diagnostic('hierarchy-broken', entity, parent));\n }\n }\n\n const state = new Map<EntityHandle, 0 | 1 | 2>();\n const stack: EntityHandle[] = [];\n const cycleMembers = new Set<EntityHandle>();\n const visit = (entity: EntityHandle): void => {\n const currentState = state.get(entity) ?? 0;\n if (currentState === 2) return;\n if (currentState === 1) {\n const cycleStart = stack.indexOf(entity);\n for (let index = cycleStart; index >= 0 && index < stack.length; index++) {\n const member = stack[index];\n if (member !== undefined) cycleMembers.add(member);\n }\n return;\n }\n\n state.set(entity, 1);\n stack.push(entity);\n const parent = parentOf.get(entity);\n if (parent !== undefined) visit(parent);\n stack.pop();\n state.set(entity, 2);\n };\n\n for (const entity of liveEntities) visit(entity);\n for (const entity of cycleMembers) {\n const parent = authoredParents.get(entity);\n if (parent !== undefined) diagnostics.push(diagnostic('hierarchy-cycle', entity, parent));\n parentOf.delete(entity);\n }\n\n diagnostics.sort((left, right) => {\n const entityDelta = (left.detail.entity as number) - (right.detail.entity as number);\n if (entityDelta !== 0) return entityDelta;\n return left.code.localeCompare(right.code);\n });\n\n const stableParentOf = new Map(parentOf);\n const stableDiagnostics = Object.freeze(diagnostics.slice());\n const snapshot: SceneHierarchySnapshot = {\n parentOf: stableParentOf,\n diagnostics: stableDiagnostics,\n getParent(entity: EntityHandle): EntityHandle | undefined {\n return stableParentOf.get(entity);\n },\n };\n HIERARCHY_PROJECTION_CACHE.set(world, {\n changes,\n snapshot,\n });\n return snapshot;\n}\n","import type { Component, World } from '@forgeax/engine-ecs';\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { ChildOf } from './components/child-of';\nimport { Children } from './components/children';\nimport { MorphWeights } from './components/morph-weights';\nimport { Name } from './components/name';\nimport { Transform } from './components/transform';\nimport { registerPropagateTransforms } from './systems/propagate-transforms';\n\nconst SCENE_COMPONENTS: readonly Component[] = [ChildOf, Children, MorphWeights, Name, Transform];\n\nfunction registerSceneComponents(world: World): () => void {\n const leases = SCENE_COMPONENTS.map((component) => world.components.register(component).unwrap());\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n\nexport function scenePlugin(): Plugin {\n return {\n name: 'scene',\n inject: ['world'],\n apply(ctx) {\n ctx.effect(() => registerSceneComponents(ctx.world), 'scene/components');\n ctx.effect(() => registerPropagateTransforms(ctx.world), 'scene/propagate-transforms');\n },\n };\n}\n"],"mappings":"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","names":["componentSchema","componentSchema","value","defineComponent","defineComponent","createWorldProjection","createWorldProjection","published","active"]}