@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
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export { skeletonContribution, skinContribution } from './assets/skin-decoder';
2
+ export {
3
+ JointCountMismatchError,
4
+ JointEntityDanglingError,
5
+ SkeletonResolveFailedError,
6
+ type SkinError,
7
+ type SkinErrorCode,
8
+ SkinInstancesCoexistForbiddenError,
9
+ SkinJointCountExceededError,
10
+ SkinJointDespawnedError,
11
+ SkinJointPathUnresolvedError,
12
+ } from './errors';
13
+ export { skinningPlugin } from './plugin';
14
+ export { resolveSkinJoints } from './resolve-skin-joints';
15
+ export { Skin } from './skin';
package/src/plugin.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { Component, World } from '@forgeax/engine-ecs';
2
+ import type { Plugin } from '@forgeax/engine-plugin';
3
+ import { Skin } from './skin';
4
+
5
+ const SKINNING_COMPONENTS: readonly Component[] = [Skin];
6
+
7
+ function registerSkinningComponents(world: World): () => void {
8
+ const leases = SKINNING_COMPONENTS.map((component) =>
9
+ world.components.register(component).unwrap(),
10
+ );
11
+ return () => {
12
+ for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
13
+ };
14
+ }
15
+
16
+ /** Install skeletal binding components in a World that consumes skinned scenes. */
17
+ export function skinningPlugin(): Plugin {
18
+ return {
19
+ name: 'skinning',
20
+ inject: ['world'],
21
+ apply(ctx) {
22
+ ctx.effect(() => registerSkinningComponents(ctx.world), 'skinning/components');
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,24 @@
1
+ import type { EntityHandle } from '@forgeax/engine-ecs';
2
+ import { type SkinError, SkinJointPathUnresolvedError } from './errors.js';
3
+
4
+ export function resolveSkinJoints(
5
+ jointPaths: readonly string[],
6
+ names: ReadonlyMap<string, EntityHandle>,
7
+ skinEntity: EntityHandle,
8
+ ): { ok: true; value: Uint32Array } | { ok: false; error: SkinError } {
9
+ const joints: number[] = [];
10
+ for (const path of jointPaths) {
11
+ const segments = path.split('/').filter(Boolean);
12
+ if (segments.length === 0) continue;
13
+ const failedAtIndex = segments.length - 1;
14
+ const entity = names.get(segments[failedAtIndex] ?? '');
15
+ if (entity === undefined) {
16
+ return {
17
+ ok: false,
18
+ error: new SkinJointPathUnresolvedError(skinEntity as number, segments, failedAtIndex),
19
+ };
20
+ }
21
+ joints.push(entity as number);
22
+ }
23
+ return { ok: true, value: new Uint32Array(joints) };
24
+ }
package/src/skin.ts ADDED
@@ -0,0 +1,68 @@
1
+ // @forgeax/engine-runtime - Skin component (skeleton handle + joint Entity slots).
2
+ //
3
+ // Schema: { skeleton: 'shared<SkeletonAsset>', joints: 'array<entity>' }.
4
+ //
5
+ // `skeleton` carries the immutable SkeletonAsset handle (IBM + jointCount);
6
+ // `joints` carries the live Entity[] resolved at post-spawn time from the
7
+ // SkinAsset.jointPaths via Name-component BFS/DFS lookup. The joint list is
8
+ // consumed by advanceAnimationPlayer (write target) and render-system-extract
9
+ // (CPU palette pre-multiply source).
10
+ //
11
+ // Naming: single-semantic component drops the 'Component' suffix
12
+ // (AGENTS.md §Component naming rule #1). `joints` field takes the holder's
13
+ // perspective (AGENTS.md §Component naming rule #3).
14
+ //
15
+ // Component registered alongside MeshFilter / MeshRenderer / Transform as
16
+ // a sibling on the same entity (AC-13 / AC-37). Skin + Instances coexistence
17
+ // on the same entity is forbidden (M2 fail-fast 'skin-instances-coexist-forbidden').
18
+ //
19
+ // Decision anchors:
20
+ // - requirements AC-13 (Skin sibling to MeshFilter / MeshRenderer)
21
+ // - requirements AC-15 (joint Entity slots, no marker component)
22
+ // - requirements AC-37 (no Component suffix)
23
+ // - plan-strategy D-10 (SkinPaletteSlice naming + Skin x Instances fail-fast)
24
+ // - charter P3 (explicit failure: joint despawn fail-fast)
25
+ // - schema vocab 'shared<SkeletonAsset>' v1 missing item #4 alignment
26
+ //
27
+ // ## Transform contract (post-bug-20260615 fix)
28
+ //
29
+ // **Old (buggy) implicit contract (pre-bug-20260615):** The Skin entity's
30
+ // Transform.world was double-applied during skinning -- the shader computed
31
+ // `world = meshes[0].worldFromLocal x palette x pos`, so any non-identity
32
+ // Transform on the Skin entity (or its non-joint ancestors) caused doubled
33
+ // motion (translation 2x, rotation 2x). Holders had to manually pin the Skin
34
+ // entity's Transform to identity to avoid doubled motion. This contract was
35
+ // undocumented and easy to violate.
36
+ //
37
+ // **New explicit contract (post-bug-20260615 fix):** An entity carrying `Skin`
38
+ // has its own `Transform` ignored at render time; the world transform is
39
+ // determined entirely by the joints' world matrices fed through the palette:
40
+ //
41
+ // palette[i] = jointWorld_i x IBM_i
42
+ // shader: world = palette[i] x pos
43
+ //
44
+ // No additional left-multiply by `meshes[0].worldFromLocal` or `instanceLocal`.
45
+ // To move the rig, parent the joint root (or any common ancestor of the joints
46
+ // in `Skin.joints[]`) to your driving entity -- moving the Skin entity itself
47
+ // has no rendering effect. This aligns with glTF 2.0 SSkins Implementation
48
+ // Note: "the transform of the node that the mesh is attached to must be
49
+ // ignored when performing skinning."
50
+ //
51
+ // Full pipeline documentation: packages/runtime/README.md
52
+ // SSkinPaletteAllocator.
53
+ //
54
+ // Fix commits:
55
+ // - M0 (red): 15425c2b -- parented skin double-transform unit test
56
+ // - M1 (green): 2ad509b7 -- shader Plan A: drop meshes[0] left-multiply
57
+ // - M2 (cleanup): 4118e463 -- extract.ts joint read -> world.get API
58
+ // - M3 (demo): 94d7db66 -- hello-skin parented Fox under non-identity rig
59
+ // - M4 (baseline): b6ddf46d -- palette-hash counter-proof + submodule pointer
60
+
61
+ import { defineComponent } from '@forgeax/engine-ecs';
62
+
63
+ export const Skin = defineComponent('Skin', {
64
+ // The renderer owns the live skeleton asset/palette binding; joint entity
65
+ // relationships remain the portable simulation-side pose contract.
66
+ skeleton: { type: 'shared<SkeletonAsset>' },
67
+ joints: { type: 'array<entity>' },
68
+ });