@forgeax/engine-skinning 0.0.0-dev.8d955ade1c79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +24 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/binding.unit.test.d.ts +2 -0
  5. package/dist/__tests__/binding.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/errors.unit.test.d.ts +2 -0
  7. package/dist/__tests__/errors.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/plugin.integration.test.d.ts +2 -0
  9. package/dist/__tests__/plugin.integration.test.d.ts.map +1 -0
  10. package/dist/__tests__/skin-error-code-owner.test-d.d.ts +27 -0
  11. package/dist/__tests__/skin-error-code-owner.test-d.d.ts.map +1 -0
  12. package/dist/assets/skin-decoder.d.ts +4 -0
  13. package/dist/assets/skin-decoder.d.ts.map +1 -0
  14. package/dist/errors.d.ts +232 -0
  15. package/dist/errors.d.ts.map +1 -0
  16. package/dist/index.d.ts +6 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.mjs +375 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/plugin.d.ts +4 -0
  21. package/dist/plugin.d.ts.map +1 -0
  22. package/dist/resolve-skin-joints.d.ts +10 -0
  23. package/dist/resolve-skin-joints.d.ts.map +1 -0
  24. package/dist/skin.d.ts +5 -0
  25. package/dist/skin.d.ts.map +1 -0
  26. package/package.json +57 -0
  27. package/src/__tests__/binding.unit.test.ts +33 -0
  28. package/src/__tests__/errors.unit.test.ts +13 -0
  29. package/src/__tests__/plugin.integration.test.ts +23 -0
  30. package/src/__tests__/skin-error-code-owner.test-d.ts +91 -0
  31. package/src/assets/skin-decoder.ts +69 -0
  32. package/src/errors.ts +356 -0
  33. package/src/index.ts +15 -0
  34. package/src/plugin.ts +25 -0
  35. package/src/resolve-skin-joints.ts +24 -0
  36. package/src/skin.ts +68 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * feat-20260612-skin-palette-per-frame-upload M2 / m2-5 subset union.
3
+ *
4
+ * Covers the three new fail-fast extract-stage errors that fire from
5
+ * `render-system-extract.ts` `hasSkin` segment when the per-frame palette
6
+ * upload pipeline cannot resolve a slice for an entity. Single-entity
7
+ * `continue` semantics: the entity is skipped, sibling entities in the
8
+ * same frame keep extracting (plan-strategy D-5).
9
+ *
10
+ * | code | class | trigger |
11
+ * |:--|:--|:--|
12
+ * | `'skeleton-resolve-failed'` | `SkeletonResolveFailedError` | `assets.get<SkeletonAsset>(skin.skeleton)` returns null/undefined |
13
+ * | `'joint-count-mismatch'` | `JointCountMismatchError` | `Skin.joints.length !== SkeletonAsset.jointCount` |
14
+ * | `'joint-entity-dangling'` | `JointEntityDanglingError` | `Skin.joints[i]` Entity is despawned (Transform.world view undefined) |
15
+ *
16
+ * AI users discriminate via `switch (err.code)` over `RuntimeErrorCode`;
17
+ * each member narrows to its `*Error` class with structured `.detail`.
18
+ *
19
+ * NOTE: distinct from the pre-existing `'skin-joint-despawned'` /
20
+ * `'skin-joint-path-unresolved'` / `'skin-joint-count-exceeded'`
21
+ * (advanceAnimationPlayer + post-spawn jointPath resolution); plan-strategy
22
+ * D-4 forbids reusing those codes for the new extract-stage triggers.
23
+ */
24
+ export type SkinExtractErrorCode = 'skeleton-resolve-failed' | 'joint-count-mismatch' | 'joint-entity-dangling';
25
+ /**
26
+ * Detail for `RuntimeErrorCode 'skin-joint-count-exceeded'`.
27
+ *
28
+ * Emitted when a glTF skin has more than MAX_JOINTS (256) joints.
29
+ */
30
+ export interface SkinJointCountExceededDetail {
31
+ readonly jointCount: number;
32
+ readonly max: number;
33
+ }
34
+ /**
35
+ * Structured error for skin joint count exceeding the engine cap.
36
+ *
37
+ * Emitted during skin import/validation. Four-field surface:
38
+ * - `.code = 'skin-joint-count-exceeded'`
39
+ * - `.expected` — max allowed (256)
40
+ * - `.hint` — reduce joint count in the source asset
41
+ * - `.detail = { jointCount, max }` — actual vs limit
42
+ */
43
+ export declare class SkinJointCountExceededError extends Error {
44
+ readonly code: "skin-joint-count-exceeded";
45
+ readonly expected: string;
46
+ readonly hint: string;
47
+ readonly detail: SkinJointCountExceededDetail;
48
+ constructor(jointCount: number, max?: number);
49
+ }
50
+ /**
51
+ * Detail for `RuntimeErrorCode 'skin-joint-despawned'`.
52
+ *
53
+ * Emitted at extract time when a Skin.joints[i] Entity has been despawned.
54
+ */
55
+ export interface SkinJointDespawnedDetail {
56
+ readonly meshEntity: number;
57
+ readonly jointIndex: number;
58
+ }
59
+ /**
60
+ * Structured error for despawned skin joint Entity.
61
+ *
62
+ * Emitted at extract time; the mesh draw is fully skipped.
63
+ * - `.code = 'skin-joint-despawned'`
64
+ * - `.expected` — all Skin.joints alive
65
+ * - `.hint` — remove the Skin component or re-spawn joints
66
+ * - `.detail = { meshEntity, jointIndex }`
67
+ */
68
+ export declare class SkinJointDespawnedError extends Error {
69
+ readonly code: "skin-joint-despawned";
70
+ readonly expected: string;
71
+ readonly hint: string;
72
+ readonly detail: SkinJointDespawnedDetail;
73
+ constructor(meshEntity: number, jointIndex: number);
74
+ }
75
+ /**
76
+ * Detail for `RuntimeErrorCode 'skin-joint-path-unresolved'`.
77
+ *
78
+ * Emitted at post-spawn time when a jointPath leaf name cannot be found.
79
+ */
80
+ export interface SkinJointPathUnresolvedDetail {
81
+ readonly skinEntity: number;
82
+ readonly path: readonly string[];
83
+ readonly failedAtIndex: number;
84
+ }
85
+ /**
86
+ * Structured error for unresolved jointPath post-spawn.
87
+ *
88
+ * Emitted by resolveSkinJoints when Name lookup fails.
89
+ * - `.code = 'skin-joint-path-unresolved'`
90
+ * - `.expected` — Name-bearing entity exists for each jointPath leaf
91
+ * - `.hint` — verify glTF node Name preservation in the importer
92
+ * - `.detail = { skinEntity, path, failedAtIndex }`
93
+ */
94
+ export declare class SkinJointPathUnresolvedError extends Error {
95
+ readonly code: "skin-joint-path-unresolved";
96
+ readonly expected: string;
97
+ readonly hint: string;
98
+ readonly detail: SkinJointPathUnresolvedDetail;
99
+ constructor(skinEntity: number, path: readonly string[], failedAtIndex: number);
100
+ }
101
+ /**
102
+ * Detail for `RuntimeErrorCode 'skin-instances-coexist-forbidden'`.
103
+ *
104
+ * Emitted at extract time when Skin + Instances coexist on the same entity.
105
+ */
106
+ export interface SkinInstancesCoexistForbiddenDetail {
107
+ readonly entity: number;
108
+ }
109
+ /**
110
+ * Structured error for Skin + Instances coexistence on same entity.
111
+ *
112
+ * Emitted at extract time; the entity draw is skipped.
113
+ * - `.code = 'skin-instances-coexist-forbidden'`
114
+ * - `.expected` — Skin and Instances on separate entities
115
+ * - `.hint` — split skinned meshes from instanced meshes into separate entities (OOS-skin-instances-coexist)
116
+ * - `.detail = { entity }`
117
+ */
118
+ export declare class SkinInstancesCoexistForbiddenError extends Error {
119
+ readonly code: "skin-instances-coexist-forbidden";
120
+ readonly expected: string;
121
+ readonly hint: string;
122
+ readonly detail: SkinInstancesCoexistForbiddenDetail;
123
+ constructor(entity: number);
124
+ }
125
+ /**
126
+ * Detail for `RuntimeErrorCode 'skeleton-resolve-failed'`.
127
+ *
128
+ * Emitted at extract time when `assets.get<SkeletonAsset>(skin.skeleton)`
129
+ * returns `null` / `undefined`. The skeleton handle is non-zero (the entity
130
+ * declared a Skin) but the asset is not registered (importer drift /
131
+ * AssetRegistry not warmed).
132
+ */
133
+ export interface SkeletonResolveFailedDetail {
134
+ readonly entity: number;
135
+ readonly skeletonHandle: number;
136
+ }
137
+ /**
138
+ * Structured error for unresolved SkeletonAsset handle at extract time
139
+ * (feat-20260612-skin-palette-per-frame-upload M2 / m2-5).
140
+ *
141
+ * Emitted at extract time; the entity draw is skipped (continue), other
142
+ * entities in the same frame keep extracting.
143
+ * - `.code = 'skeleton-resolve-failed'`
144
+ * - `.expected` — Skin.skeleton handle resolves to a registered SkeletonAsset
145
+ * - `.hint` — verify SkeletonAsset is imported into pack-index AND registered
146
+ * via AssetRegistry.register(handle, asset) before extractFrame
147
+ * - `.detail = { entity, skeletonHandle }`
148
+ */
149
+ export declare class SkeletonResolveFailedError extends Error {
150
+ readonly code: "skeleton-resolve-failed";
151
+ readonly expected: string;
152
+ readonly hint: string;
153
+ readonly detail: SkeletonResolveFailedDetail;
154
+ constructor(entity: number, skeletonHandle: number);
155
+ }
156
+ /**
157
+ * Detail for `RuntimeErrorCode 'joint-count-mismatch'`.
158
+ *
159
+ * Emitted at extract time when `Skin.joints.length !== SkeletonAsset.jointCount`.
160
+ * `expected` is the SkeletonAsset's jointCount (the source of truth);
161
+ * `actual` is the entity's `Skin.joints.length` (the runtime entity reference
162
+ * list materialized at post-spawn time).
163
+ */
164
+ export interface JointCountMismatchDetail {
165
+ readonly entity: number;
166
+ readonly expected: number;
167
+ readonly actual: number;
168
+ }
169
+ /**
170
+ * Structured error for SkinAsset.joints[] vs SkeletonAsset.jointCount disagreement
171
+ * (feat-20260612-skin-palette-per-frame-upload M2 / m2-5).
172
+ *
173
+ * Emitted at extract time; the entity draw is skipped (continue).
174
+ * - `.code = 'joint-count-mismatch'`
175
+ * - `.expected` — Skin.joints.length === SkeletonAsset.jointCount
176
+ * - `.hint` — verify SkinAsset.joints[] and SkeletonAsset jointPaths[]
177
+ * come from the same glTF skin node
178
+ * - `.detail = { entity, expected, actual }`
179
+ */
180
+ export declare class JointCountMismatchError extends Error {
181
+ readonly code: "joint-count-mismatch";
182
+ readonly expected: string;
183
+ readonly hint: string;
184
+ readonly detail: JointCountMismatchDetail;
185
+ constructor(entity: number, expected: number, actual: number);
186
+ }
187
+ /**
188
+ * Detail for `RuntimeErrorCode 'joint-entity-dangling'`.
189
+ *
190
+ * Emitted at extract time when `Skin.joints[i]` points at an Entity that has
191
+ * been despawned (or lost its Transform component) so
192
+ * `worldInternal._getArrayView(jointEntity, Transform, 'world')` returns
193
+ * undefined. `jointIndex` is the position within `Skin.joints[]`.
194
+ */
195
+ export interface JointEntityDanglingDetail {
196
+ readonly entity: number;
197
+ readonly jointIndex: number;
198
+ }
199
+ /**
200
+ * Structured error for despawned (or Transform-less) joint Entity at extract
201
+ * time (feat-20260612-skin-palette-per-frame-upload M2 / m2-5).
202
+ *
203
+ * Distinct from the pre-existing `SkinJointDespawnedError` which fires from
204
+ * advanceAnimationPlayer (animation-stage); this one fires from extractFrame
205
+ * (palette-upload stage) when the per-joint world mat4 view is missing.
206
+ *
207
+ * Emitted at extract time; the entity draw is skipped (continue).
208
+ * - `.code = 'joint-entity-dangling'`
209
+ * - `.expected` — Skin.joints[i] references a live Entity with Transform
210
+ * - `.hint` — sync Skin.joints[] when joint entities are despawned, or
211
+ * re-import the scene through the gltf importer to refresh Entity refs
212
+ * - `.detail = { entity, jointIndex }`
213
+ */
214
+ export declare class JointEntityDanglingError extends Error {
215
+ readonly code: "joint-entity-dangling";
216
+ readonly expected: string;
217
+ readonly hint: string;
218
+ readonly detail: JointEntityDanglingDetail;
219
+ constructor(entity: number, jointIndex: number);
220
+ }
221
+ /**
222
+ * Closed union of skin-cluster error codes derived from the correlated error
223
+ * union. AI users perform exhaustive `switch (err.code)` without default; TS
224
+ * guards completeness.
225
+ */
226
+ export type SkinErrorCode = SkinError['code'];
227
+ /**
228
+ * Closed union of the skin-cluster structured error classes, each carrying a
229
+ * `SkinErrorCode` discriminant on `.code`.
230
+ */
231
+ export type SkinError = SkinJointCountExceededError | SkinJointDespawnedError | SkinJointPathUnresolvedError | SkinInstancesCoexistForbiddenError | SkeletonResolveFailedError | JointCountMismatchError | JointEntityDanglingError;
232
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export { skeletonContribution, skinContribution } from './assets/skin-decoder';
2
+ export { JointCountMismatchError, JointEntityDanglingError, SkeletonResolveFailedError, type SkinError, type SkinErrorCode, SkinInstancesCoexistForbiddenError, SkinJointCountExceededError, SkinJointDespawnedError, SkinJointPathUnresolvedError, } from './errors';
3
+ export { skinningPlugin } from './plugin';
4
+ export { resolveSkinJoints } from './resolve-skin-joints';
5
+ export { Skin } from './skin';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,0BAA0B,EAC1B,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,kCAAkC,EAClC,2BAA2B,EAC3B,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
package/dist/index.mjs ADDED
@@ -0,0 +1,375 @@
1
+ // ../types/dist/index.mjs
2
+ var OK_PROTO = {
3
+ unwrap() {
4
+ return this.value;
5
+ },
6
+ unwrapOr(_defaultValue) {
7
+ return this.value;
8
+ }
9
+ };
10
+ var ERR_PROTO = {
11
+ unwrap() {
12
+ throw this.error;
13
+ },
14
+ unwrapOr(defaultValue) {
15
+ return defaultValue;
16
+ }
17
+ };
18
+ function ok(value) {
19
+ const r = Object.create(OK_PROTO);
20
+ r.ok = true;
21
+ r.value = value;
22
+ return r;
23
+ }
24
+ function err(error) {
25
+ const r = Object.create(ERR_PROTO);
26
+ r.ok = false;
27
+ r.error = error;
28
+ return r;
29
+ }
30
+ var MAX_SLOT = (1 << 24) - 1;
31
+ var MATERIAL_ERROR_CODES = [
32
+ "material-parent-not-found",
33
+ "material-circular-inheritance",
34
+ "material-no-effective-pass",
35
+ "material-value-unknown",
36
+ "material-value-type-mismatch",
37
+ "material-contract-program-mismatch",
38
+ "shader-module-id-missing",
39
+ "shader-module-id-duplicate",
40
+ "shader-module-not-found",
41
+ "shader-module-namespace-reserved",
42
+ "material-reflection-binding-mismatch",
43
+ "material-specialization-not-cooked",
44
+ "material-specialization-stale-generation",
45
+ "gltf-material-uv-set-missing",
46
+ "material-derived-interface-mismatch",
47
+ "material-texture-coordinate-invalid",
48
+ "material-payload-bounds"
49
+ ];
50
+ var MATERIAL_ERROR_POLICY = {
51
+ "material-parent-not-found": {
52
+ expected: "every parent GUID resolves to a MaterialAsset",
53
+ hint: "fix the parent GUID and resolve the material again"
54
+ },
55
+ "material-circular-inheritance": {
56
+ expected: "the parent chain is acyclic",
57
+ hint: "remove the repeated GUID from the parent chain"
58
+ },
59
+ "material-no-effective-pass": {
60
+ expected: "the resolved material has at least one pass",
61
+ hint: "add a pass to the root material or an inherited parent"
62
+ },
63
+ "material-value-unknown": {
64
+ expected: "every value name is declared by the effective contract",
65
+ hint: "remove the value or declare the parameter in the root contract"
66
+ },
67
+ "material-value-type-mismatch": {
68
+ expected: "each value matches its declared parameter type",
69
+ hint: "change the value to the declared parameter type"
70
+ },
71
+ "material-contract-program-mismatch": {
72
+ expected: "the program satisfies the material contract",
73
+ hint: "align the program entries with the root contract"
74
+ },
75
+ "shader-module-id-missing": {
76
+ expected: "each WGSL source declares a module ID",
77
+ hint: "add a compiler-native module ID declaration to the WGSL source"
78
+ },
79
+ "shader-module-id-duplicate": {
80
+ expected: "each module ID has one source provenance",
81
+ hint: "rename one module or remove the duplicate source"
82
+ },
83
+ "shader-module-not-found": {
84
+ expected: "every referenced module exists in the source catalog",
85
+ hint: "add the module to the source catalog or fix the reference"
86
+ },
87
+ "shader-module-namespace-reserved": {
88
+ expected: "user modules use a non-reserved namespace",
89
+ hint: "choose a module ID outside the reserved namespace"
90
+ },
91
+ "material-reflection-binding-mismatch": {
92
+ expected: "reflection matches the material contract bindings",
93
+ hint: "update the contract or WGSL binding and cook again"
94
+ },
95
+ "material-specialization-not-cooked": {
96
+ expected: "the requested specialization has a cooked artifact",
97
+ hint: "run the build or development cook path for this selection"
98
+ },
99
+ "material-specialization-stale-generation": {
100
+ expected: "all specialization dependencies share one generation",
101
+ hint: "retry after dependent assets and sources settle"
102
+ },
103
+ "gltf-material-uv-set-missing": {
104
+ expected: "each texture slot references an available primitive UV set",
105
+ hint: "add the requested UV set to the primitive and re-import it"
106
+ },
107
+ "material-derived-interface-mismatch": {
108
+ expected: "the generated material interface matches the derived schema interface",
109
+ hint: "repair the schema or WGSL producer and recook the material"
110
+ },
111
+ "material-texture-coordinate-invalid": {
112
+ expected: "every texture coordinate record is finite and complete",
113
+ hint: "repair the texture metadata or coordinates and recook the material"
114
+ },
115
+ "material-payload-bounds": {
116
+ expected: "every material payload write stays within the derived payload",
117
+ hint: "repair the derived payload owner before submitting the draw"
118
+ }
119
+ };
120
+ var MATERIAL_ERROR_EXPECTED = Object.fromEntries(
121
+ MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].expected])
122
+ );
123
+ var MATERIAL_ERROR_HINTS = Object.fromEntries(
124
+ MATERIAL_ERROR_CODES.map((code) => [code, MATERIAL_ERROR_POLICY[code].hint])
125
+ );
126
+ var NUMERIC_TYPES = /* @__PURE__ */ new Set([
127
+ "f32",
128
+ "i32",
129
+ "u32",
130
+ "vec2",
131
+ "vec3",
132
+ "vec4",
133
+ "color"
134
+ ]);
135
+ var TEXTURE_VIEW_TYPES = /* @__PURE__ */ new Set([
136
+ "texture2d",
137
+ "texture_cube",
138
+ "texture_depth_2d",
139
+ "texture_cube_array"
140
+ ]);
141
+ var SAMPLER_TYPES = /* @__PURE__ */ new Set([
142
+ "sampler",
143
+ "sampler_comparison"
144
+ ]);
145
+ var ALL_TYPES = /* @__PURE__ */ new Set([
146
+ ...NUMERIC_TYPES,
147
+ ...TEXTURE_VIEW_TYPES,
148
+ ...SAMPLER_TYPES,
149
+ "storage_buffer"
150
+ ]);
151
+
152
+ // src/assets/skin-decoder.ts
153
+ function floatArray(value) {
154
+ if (value instanceof Float32Array) return value;
155
+ if (Array.isArray(value) && value.every((item) => typeof item === "number")) {
156
+ return Float32Array.from(value);
157
+ }
158
+ return void 0;
159
+ }
160
+ var skinContribution = {
161
+ kind: { kind: "skin" },
162
+ consumer: "resolveSkinJoints",
163
+ decoder: {
164
+ async decode({ envelope }) {
165
+ const payload = envelope.payload;
166
+ if (payload.kind === "skin" && payload.skeletonGuid.length > 0 && payload.jointPaths.length > 0) {
167
+ return ok(payload);
168
+ }
169
+ return err({
170
+ code: "asset-package-invalid",
171
+ expected: "a skin payload with a skeleton GUID and joint paths",
172
+ hint: "recook the skin binding and publish its skeleton reference",
173
+ detail: { guid: envelope.guid, reason: "skin owner validation failed" }
174
+ });
175
+ }
176
+ }
177
+ };
178
+ var skeletonContribution = {
179
+ kind: { kind: "skeleton" },
180
+ consumer: "resolveSkinJoints",
181
+ decoder: {
182
+ async decode({ envelope }) {
183
+ const payload = envelope.payload;
184
+ if (payload !== null && typeof payload === "object") {
185
+ const source = payload;
186
+ const inverseBindMatrices = floatArray(source.inverseBindMatrices);
187
+ const jointCount = source.jointCount;
188
+ if (source.kind === "skeleton" && inverseBindMatrices !== void 0 && Number.isSafeInteger(jointCount) && jointCount >= 0 && inverseBindMatrices.length === jointCount * 16) {
189
+ return ok({ kind: "skeleton", inverseBindMatrices, jointCount });
190
+ }
191
+ }
192
+ return err({
193
+ code: "asset-package-invalid",
194
+ expected: "a skeleton payload with one inverse-bind matrix per joint",
195
+ hint: "recook the skeleton and publish its complete joint data",
196
+ detail: { guid: envelope.guid, reason: "skeleton owner validation failed" }
197
+ });
198
+ }
199
+ }
200
+ };
201
+
202
+ // src/errors.ts
203
+ var SkinJointCountExceededError = class extends Error {
204
+ code = "skin-joint-count-exceeded";
205
+ expected;
206
+ hint;
207
+ detail;
208
+ constructor(jointCount, max = 256) {
209
+ const expected = `jointCount <= ${max}`;
210
+ const hint = `skin has ${jointCount} joints (max ${max}); reduce joint count in the source glTF asset (OOS-skin-many-joints)`;
211
+ super(`skin joint count ${jointCount} exceeds max ${max}`);
212
+ this.name = "SkinJointCountExceededError";
213
+ this.expected = expected;
214
+ this.hint = hint;
215
+ this.detail = { jointCount, max };
216
+ }
217
+ };
218
+ var SkinJointDespawnedError = class extends Error {
219
+ code = "skin-joint-despawned";
220
+ expected;
221
+ hint;
222
+ detail;
223
+ constructor(meshEntity, jointIndex) {
224
+ const expected = `Skin.joints[${jointIndex}] references a live entity`;
225
+ const hint = `joint[${jointIndex}] of entity ${meshEntity} has been despawned; remove Skin component or re-spawn the joint entity (OOS-skin-joint-respawn)`;
226
+ super(`skin joint[${jointIndex}] despawned for entity ${meshEntity}`);
227
+ this.name = "SkinJointDespawnedError";
228
+ this.expected = expected;
229
+ this.hint = hint;
230
+ this.detail = { meshEntity, jointIndex };
231
+ }
232
+ };
233
+ var SkinJointPathUnresolvedError = class extends Error {
234
+ code = "skin-joint-path-unresolved";
235
+ expected;
236
+ hint;
237
+ detail;
238
+ constructor(skinEntity, path, failedAtIndex) {
239
+ const leafName = path[failedAtIndex] ?? "<unknown>";
240
+ const expected = `joint entity with Name="${leafName}" exists in the world`;
241
+ const hint = `joint path "${path.join("/")}" for skin entity ${skinEntity} could not be resolved; verify glTF node names are preserved`;
242
+ super(
243
+ `joint path "${path.join("/")}" unresolved at index ${failedAtIndex} for entity ${skinEntity}`
244
+ );
245
+ this.name = "SkinJointPathUnresolvedError";
246
+ this.expected = expected;
247
+ this.hint = hint;
248
+ this.detail = { skinEntity, path, failedAtIndex };
249
+ }
250
+ };
251
+ var SkinInstancesCoexistForbiddenError = class extends Error {
252
+ code = "skin-instances-coexist-forbidden";
253
+ expected;
254
+ hint;
255
+ detail;
256
+ constructor(entity) {
257
+ const expected = "Skin and Instances must not coexist on the same entity";
258
+ const hint = `entity ${entity} has both Skin and Instances; split skinned meshes from instanced meshes into separate entities (OOS-skin-instances-coexist)`;
259
+ super(`Skin + Instances coexistence forbidden on entity ${entity}`);
260
+ this.name = "SkinInstancesCoexistForbiddenError";
261
+ this.expected = expected;
262
+ this.hint = hint;
263
+ this.detail = { entity };
264
+ }
265
+ };
266
+ var SkeletonResolveFailedError = class extends Error {
267
+ code = "skeleton-resolve-failed";
268
+ expected;
269
+ hint;
270
+ detail;
271
+ constructor(entity, skeletonHandle) {
272
+ const expected = `Skin.skeleton handle ${skeletonHandle} resolves to a registered SkeletonAsset`;
273
+ const hint = `entity ${entity} Skin.skeleton handle ${skeletonHandle} is not registered; check that the SkeletonAsset went through the gltf importer into pack-index AND that AssetRegistry.register was called for the handle before extractFrame runs`;
274
+ super(`Skin skeleton resolve failed on entity ${entity}: handle ${skeletonHandle}`);
275
+ this.name = "SkeletonResolveFailedError";
276
+ this.expected = expected;
277
+ this.hint = hint;
278
+ this.detail = { entity, skeletonHandle };
279
+ }
280
+ };
281
+ var JointCountMismatchError = class extends Error {
282
+ code = "joint-count-mismatch";
283
+ expected;
284
+ hint;
285
+ detail;
286
+ constructor(entity, expected, actual) {
287
+ const expectedStr = `Skin.joints.length === SkeletonAsset.jointCount (=${expected})`;
288
+ const hint = `entity ${entity}: Skin.joints.length=${actual} disagrees with SkeletonAsset.jointCount=${expected}; verify SkinAsset.joints[] and SkeletonAsset jointPaths[] come from the same glTF skin node`;
289
+ super(
290
+ `joint count mismatch on entity ${entity}: SkeletonAsset.jointCount=${expected}, Skin.joints.length=${actual}`
291
+ );
292
+ this.name = "JointCountMismatchError";
293
+ this.expected = expectedStr;
294
+ this.hint = hint;
295
+ this.detail = { entity, expected, actual };
296
+ }
297
+ };
298
+ var JointEntityDanglingError = class extends Error {
299
+ code = "joint-entity-dangling";
300
+ expected;
301
+ hint;
302
+ detail;
303
+ constructor(entity, jointIndex) {
304
+ const expected = `Skin.joints[${jointIndex}] references a live Entity with Transform`;
305
+ const hint = `entity ${entity} Skin.joints[${jointIndex}] points at a despawned (or Transform-less) Entity; sync Skin.joints[] when joint entities are despawned, or re-import the scene through the gltf importer to refresh Entity references`;
306
+ super(`joint entity dangling on entity ${entity} at jointIndex ${jointIndex}`);
307
+ this.name = "JointEntityDanglingError";
308
+ this.expected = expected;
309
+ this.hint = hint;
310
+ this.detail = { entity, jointIndex };
311
+ }
312
+ };
313
+
314
+ // src/skin.ts
315
+ import { defineComponent } from "@forgeax/engine-ecs";
316
+ var Skin = defineComponent("Skin", {
317
+ // The renderer owns the live skeleton asset/palette binding; joint entity
318
+ // relationships remain the portable simulation-side pose contract.
319
+ skeleton: { type: "shared<SkeletonAsset>" },
320
+ joints: { type: "array<entity>" }
321
+ });
322
+
323
+ // src/plugin.ts
324
+ var SKINNING_COMPONENTS = [Skin];
325
+ function registerSkinningComponents(world) {
326
+ const leases = SKINNING_COMPONENTS.map(
327
+ (component) => world.components.register(component).unwrap()
328
+ );
329
+ return () => {
330
+ for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
331
+ };
332
+ }
333
+ function skinningPlugin() {
334
+ return {
335
+ name: "skinning",
336
+ inject: ["world"],
337
+ apply(ctx) {
338
+ ctx.effect(() => registerSkinningComponents(ctx.world), "skinning/components");
339
+ }
340
+ };
341
+ }
342
+
343
+ // src/resolve-skin-joints.ts
344
+ function resolveSkinJoints(jointPaths, names, skinEntity) {
345
+ const joints = [];
346
+ for (const path of jointPaths) {
347
+ const segments = path.split("/").filter(Boolean);
348
+ if (segments.length === 0) continue;
349
+ const failedAtIndex = segments.length - 1;
350
+ const entity = names.get(segments[failedAtIndex] ?? "");
351
+ if (entity === void 0) {
352
+ return {
353
+ ok: false,
354
+ error: new SkinJointPathUnresolvedError(skinEntity, segments, failedAtIndex)
355
+ };
356
+ }
357
+ joints.push(entity);
358
+ }
359
+ return { ok: true, value: new Uint32Array(joints) };
360
+ }
361
+ export {
362
+ JointCountMismatchError,
363
+ JointEntityDanglingError,
364
+ SkeletonResolveFailedError,
365
+ Skin,
366
+ SkinInstancesCoexistForbiddenError,
367
+ SkinJointCountExceededError,
368
+ SkinJointDespawnedError,
369
+ SkinJointPathUnresolvedError,
370
+ resolveSkinJoints,
371
+ skeletonContribution,
372
+ skinContribution,
373
+ skinningPlugin
374
+ };
375
+ //# sourceMappingURL=index.mjs.map