@forgeax/engine-gltf 0.1.2

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 (217) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +197 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/__tests__/animation-target-errors.unit.test.d.ts +2 -0
  5. package/dist/__tests__/animation-target-errors.unit.test.d.ts.map +1 -0
  6. package/dist/__tests__/asset-runtime-fixture.d.ts +14 -0
  7. package/dist/__tests__/asset-runtime-fixture.d.ts.map +1 -0
  8. package/dist/__tests__/bridge-material-samplers.unit.test.d.ts +2 -0
  9. package/dist/__tests__/bridge-material-samplers.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/bridge-material-values.unit.test.d.ts +2 -0
  11. package/dist/__tests__/bridge-material-values.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts +2 -0
  13. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts.map +1 -0
  14. package/dist/__tests__/bridge-sampler-required.test-d.d.ts +2 -0
  15. package/dist/__tests__/bridge-sampler-required.test-d.d.ts.map +1 -0
  16. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts +2 -0
  17. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts +2 -0
  19. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts.map +1 -0
  20. package/dist/__tests__/bridge-test-helpers.d.ts +5 -0
  21. package/dist/__tests__/bridge-test-helpers.d.ts.map +1 -0
  22. package/dist/__tests__/bridge.unit.test.d.ts +2 -0
  23. package/dist/__tests__/bridge.unit.test.d.ts.map +1 -0
  24. package/dist/__tests__/cli-gltf.integration.test.d.ts +2 -0
  25. package/dist/__tests__/cli-gltf.integration.test.d.ts.map +1 -0
  26. package/dist/__tests__/color-0-accessor.unit.test.d.ts +2 -0
  27. package/dist/__tests__/color-0-accessor.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/data-uri-surface.unit.test.d.ts +2 -0
  29. package/dist/__tests__/data-uri-surface.unit.test.d.ts.map +1 -0
  30. package/dist/__tests__/gltf-error-derived-views.test-d.d.ts +2 -0
  31. package/dist/__tests__/gltf-error-derived-views.test-d.d.ts.map +1 -0
  32. package/dist/__tests__/gltf-error-migration.test.d.ts +2 -0
  33. package/dist/__tests__/gltf-error-migration.test.d.ts.map +1 -0
  34. package/dist/__tests__/gltf-error-policy-owner.unit.test.d.ts +2 -0
  35. package/dist/__tests__/gltf-error-policy-owner.unit.test.d.ts.map +1 -0
  36. package/dist/__tests__/gltf-importer.test.d.ts +2 -0
  37. package/dist/__tests__/gltf-importer.test.d.ts.map +1 -0
  38. package/dist/__tests__/gltf-local-artifacts.test.d.ts +2 -0
  39. package/dist/__tests__/gltf-local-artifacts.test.d.ts.map +1 -0
  40. package/dist/__tests__/gltf.unit.test.d.ts +2 -0
  41. package/dist/__tests__/gltf.unit.test.d.ts.map +1 -0
  42. package/dist/__tests__/malformed-buffer-data-uri.integration.test.d.ts +2 -0
  43. package/dist/__tests__/malformed-buffer-data-uri.integration.test.d.ts.map +1 -0
  44. package/dist/__tests__/material-missing-uv.unit.test.d.ts +2 -0
  45. package/dist/__tests__/material-missing-uv.unit.test.d.ts.map +1 -0
  46. package/dist/__tests__/material-pack-refs.integration.test.d.ts +2 -0
  47. package/dist/__tests__/material-pack-refs.integration.test.d.ts.map +1 -0
  48. package/dist/__tests__/material-per-slot-ir.unit.test.d.ts +2 -0
  49. package/dist/__tests__/material-per-slot-ir.unit.test.d.ts.map +1 -0
  50. package/dist/__tests__/material-texture-transform.unit.test.d.ts +2 -0
  51. package/dist/__tests__/material-texture-transform.unit.test.d.ts.map +1 -0
  52. package/dist/__tests__/material-unused-uv.unit.test.d.ts +2 -0
  53. package/dist/__tests__/material-unused-uv.unit.test.d.ts.map +1 -0
  54. package/dist/__tests__/meshopt-decode.unit.test.d.ts +2 -0
  55. package/dist/__tests__/meshopt-decode.unit.test.d.ts.map +1 -0
  56. package/dist/__tests__/meshopt-falsifier.unit.test.d.ts +2 -0
  57. package/dist/__tests__/meshopt-falsifier.unit.test.d.ts.map +1 -0
  58. package/dist/__tests__/meshopt-mode-filter-owner.test-d.d.ts +2 -0
  59. package/dist/__tests__/meshopt-mode-filter-owner.test-d.d.ts.map +1 -0
  60. package/dist/__tests__/meshopt-real-matrix.integration.test.d.ts +2 -0
  61. package/dist/__tests__/meshopt-real-matrix.integration.test.d.ts.map +1 -0
  62. package/dist/__tests__/morph-import.integration.test.d.ts +2 -0
  63. package/dist/__tests__/morph-import.integration.test.d.ts.map +1 -0
  64. package/dist/__tests__/morph-import.unit.test.d.ts +2 -0
  65. package/dist/__tests__/morph-import.unit.test.d.ts.map +1 -0
  66. package/dist/__tests__/parse-decode.unit.test.d.ts +2 -0
  67. package/dist/__tests__/parse-decode.unit.test.d.ts.map +1 -0
  68. package/dist/__tests__/parse-gltf-multi-uv.test.d.ts +2 -0
  69. package/dist/__tests__/parse-gltf-multi-uv.test.d.ts.map +1 -0
  70. package/dist/__tests__/parse-gltf-skin-attrs.unit.test.d.ts +2 -0
  71. package/dist/__tests__/parse-gltf-skin-attrs.unit.test.d.ts.map +1 -0
  72. package/dist/__tests__/parse-gltf.unit.test.d.ts +2 -0
  73. package/dist/__tests__/parse-gltf.unit.test.d.ts.map +1 -0
  74. package/dist/__tests__/pick-e2e.integration.test.d.ts +2 -0
  75. package/dist/__tests__/pick-e2e.integration.test.d.ts.map +1 -0
  76. package/dist/__tests__/public-surface.unit.test.d.ts +2 -0
  77. package/dist/__tests__/public-surface.unit.test.d.ts.map +1 -0
  78. package/dist/__tests__/source-key-producer.integration.test.d.ts +2 -0
  79. package/dist/__tests__/source-key-producer.integration.test.d.ts.map +1 -0
  80. package/dist/__tests__/source-key.unit.test.d.ts +2 -0
  81. package/dist/__tests__/source-key.unit.test.d.ts.map +1 -0
  82. package/dist/accessor/decode-accessor.d.ts +75 -0
  83. package/dist/accessor/decode-accessor.d.ts.map +1 -0
  84. package/dist/accessor/decode-color.d.ts +15 -0
  85. package/dist/accessor/decode-color.d.ts.map +1 -0
  86. package/dist/accessor/index.d.ts +3 -0
  87. package/dist/accessor/index.d.ts.map +1 -0
  88. package/dist/bridge.d.ts +104 -0
  89. package/dist/bridge.d.ts.map +1 -0
  90. package/dist/check-extensions.d.ts +29 -0
  91. package/dist/check-extensions.d.ts.map +1 -0
  92. package/dist/cli-gltf.d.ts +8 -0
  93. package/dist/cli-gltf.d.ts.map +1 -0
  94. package/dist/cli-gltf.mjs +2419 -0
  95. package/dist/cli-gltf.mjs.map +1 -0
  96. package/dist/data-uri.d.ts +6 -0
  97. package/dist/data-uri.d.ts.map +1 -0
  98. package/dist/errors.d.ts +201 -0
  99. package/dist/errors.d.ts.map +1 -0
  100. package/dist/extract-image-bytes.d.ts +42 -0
  101. package/dist/extract-image-bytes.d.ts.map +1 -0
  102. package/dist/gltf-importer.d.ts +21 -0
  103. package/dist/gltf-importer.d.ts.map +1 -0
  104. package/dist/image-color-space.d.ts +35 -0
  105. package/dist/image-color-space.d.ts.map +1 -0
  106. package/dist/importer-entry.d.ts +10 -0
  107. package/dist/importer-entry.d.ts.map +1 -0
  108. package/dist/importer-entry.mjs +3367 -0
  109. package/dist/importer-entry.mjs.map +1 -0
  110. package/dist/index.d.ts +25 -0
  111. package/dist/index.d.ts.map +1 -0
  112. package/dist/index.mjs +3645 -0
  113. package/dist/index.mjs.map +1 -0
  114. package/dist/meshopt-decode.d.ts +39 -0
  115. package/dist/meshopt-decode.d.ts.map +1 -0
  116. package/dist/node-file-entry.d.ts +14 -0
  117. package/dist/node-file-entry.d.ts.map +1 -0
  118. package/dist/node-file-entry.mjs +1942 -0
  119. package/dist/node-file-entry.mjs.map +1 -0
  120. package/dist/node-path.d.ts +14 -0
  121. package/dist/node-path.d.ts.map +1 -0
  122. package/dist/parse-animation.d.ts +70 -0
  123. package/dist/parse-animation.d.ts.map +1 -0
  124. package/dist/parse-glb-chunks.d.ts +25 -0
  125. package/dist/parse-glb-chunks.d.ts.map +1 -0
  126. package/dist/parse-glb.d.ts +3 -0
  127. package/dist/parse-glb.d.ts.map +1 -0
  128. package/dist/parse-gltf-header.d.ts +18 -0
  129. package/dist/parse-gltf-header.d.ts.map +1 -0
  130. package/dist/parse-gltf.d.ts +263 -0
  131. package/dist/parse-gltf.d.ts.map +1 -0
  132. package/dist/parse-skin.d.ts +70 -0
  133. package/dist/parse-skin.d.ts.map +1 -0
  134. package/dist/reimport-reuse-meta.d.ts +48 -0
  135. package/dist/reimport-reuse-meta.d.ts.map +1 -0
  136. package/dist/serialize-meta.d.ts +2 -0
  137. package/dist/serialize-meta.d.ts.map +1 -0
  138. package/dist/source-key.d.ts +22 -0
  139. package/dist/source-key.d.ts.map +1 -0
  140. package/dist/sub-asset-key.d.ts +12 -0
  141. package/dist/sub-asset-key.d.ts.map +1 -0
  142. package/dist/to-asset-pack.d.ts +2 -0
  143. package/dist/to-asset-pack.d.ts.map +1 -0
  144. package/dist/transform.d.ts +37 -0
  145. package/dist/transform.d.ts.map +1 -0
  146. package/package.json +94 -0
  147. package/src/__tests__/animation-target-errors.unit.test.ts +51 -0
  148. package/src/__tests__/asset-runtime-fixture.ts +278 -0
  149. package/src/__tests__/bridge-material-samplers.unit.test.ts +67 -0
  150. package/src/__tests__/bridge-material-values.unit.test.ts +106 -0
  151. package/src/__tests__/bridge-mixed-color.unit.test.ts +100 -0
  152. package/src/__tests__/bridge-sampler-required.test-d.ts +59 -0
  153. package/src/__tests__/bridge-skin-shader-route.unit.test.ts +42 -0
  154. package/src/__tests__/bridge-skin-stride.unit.test.ts +115 -0
  155. package/src/__tests__/bridge-test-helpers.ts +9 -0
  156. package/src/__tests__/bridge.unit.test.ts +1227 -0
  157. package/src/__tests__/cli-gltf.integration.test.ts +122 -0
  158. package/src/__tests__/color-0-accessor.unit.test.ts +174 -0
  159. package/src/__tests__/data-uri-surface.unit.test.ts +26 -0
  160. package/src/__tests__/fixtures/color-0/float-vec3.gltf +13 -0
  161. package/src/__tests__/fixtures/color-0/interleaved-vec3.gltf +6 -0
  162. package/src/__tests__/fixtures/color-0/normalized-ubyte.gltf +6 -0
  163. package/src/__tests__/fixtures/color-0/normalized-ushort.gltf +6 -0
  164. package/src/__tests__/gltf-error-derived-views.test-d.ts +152 -0
  165. package/src/__tests__/gltf-error-migration.test.ts +147 -0
  166. package/src/__tests__/gltf-error-policy-owner.unit.test.ts +185 -0
  167. package/src/__tests__/gltf-importer.test.ts +12 -0
  168. package/src/__tests__/gltf-local-artifacts.test.ts +11 -0
  169. package/src/__tests__/gltf.unit.test.ts +2975 -0
  170. package/src/__tests__/malformed-buffer-data-uri.integration.test.ts +140 -0
  171. package/src/__tests__/material-missing-uv.unit.test.ts +29 -0
  172. package/src/__tests__/material-pack-refs.integration.test.ts +58 -0
  173. package/src/__tests__/material-per-slot-ir.unit.test.ts +90 -0
  174. package/src/__tests__/material-texture-transform.unit.test.ts +75 -0
  175. package/src/__tests__/material-unused-uv.unit.test.ts +44 -0
  176. package/src/__tests__/meshopt-decode.unit.test.ts +87 -0
  177. package/src/__tests__/meshopt-falsifier.unit.test.ts +92 -0
  178. package/src/__tests__/meshopt-mode-filter-owner.test-d.ts +79 -0
  179. package/src/__tests__/meshopt-real-matrix.integration.test.ts +286 -0
  180. package/src/__tests__/morph-import.integration.test.ts +128 -0
  181. package/src/__tests__/morph-import.unit.test.ts +62 -0
  182. package/src/__tests__/parse-decode.unit.test.ts +919 -0
  183. package/src/__tests__/parse-gltf-multi-uv.test.ts +238 -0
  184. package/src/__tests__/parse-gltf-skin-attrs.unit.test.ts +191 -0
  185. package/src/__tests__/parse-gltf.unit.test.ts +133 -0
  186. package/src/__tests__/pick-e2e.integration.test.ts +238 -0
  187. package/src/__tests__/public-surface.unit.test.ts +10 -0
  188. package/src/__tests__/source-key-producer.integration.test.ts +163 -0
  189. package/src/__tests__/source-key.unit.test.ts +159 -0
  190. package/src/accessor/decode-accessor.ts +309 -0
  191. package/src/accessor/decode-color.ts +128 -0
  192. package/src/accessor/index.ts +15 -0
  193. package/src/bridge.ts +929 -0
  194. package/src/check-extensions.ts +81 -0
  195. package/src/cli-gltf.ts +344 -0
  196. package/src/data-uri.ts +29 -0
  197. package/src/errors.ts +386 -0
  198. package/src/extract-image-bytes.ts +343 -0
  199. package/src/gltf-importer.ts +967 -0
  200. package/src/image-color-space.ts +104 -0
  201. package/src/importer-entry.ts +24 -0
  202. package/src/index.ts +147 -0
  203. package/src/meshopt-decode.ts +200 -0
  204. package/src/node-file-entry.ts +98 -0
  205. package/src/node-path.ts +41 -0
  206. package/src/parse-animation.ts +286 -0
  207. package/src/parse-glb-chunks.ts +167 -0
  208. package/src/parse-glb.ts +16 -0
  209. package/src/parse-gltf-header.ts +58 -0
  210. package/src/parse-gltf.ts +1729 -0
  211. package/src/parse-skin.ts +238 -0
  212. package/src/reimport-reuse-meta.ts +134 -0
  213. package/src/serialize-meta.ts +24 -0
  214. package/src/source-key.ts +72 -0
  215. package/src/sub-asset-key.ts +61 -0
  216. package/src/to-asset-pack.ts +8 -0
  217. package/src/transform.ts +117 -0
@@ -0,0 +1,59 @@
1
+ // bridge-sampler-required.test-d.ts - type-level assertions for
2
+ // toMaterialAsset (pass-based, feat-20260526-material-asset-multipass-renderstate M4 / w31).
3
+ //
4
+ // toMaterialAsset no longer takes a shadingModel parameter; it accepts
5
+ // GltfMaterialIr + optional MaterialBridgeContext and returns MaterialAsset.
6
+
7
+ import type { Handle } from '@forgeax/engine-types';
8
+ import { describe, expectTypeOf, it } from 'vitest';
9
+ import type { MaterialBridgeContext } from '../bridge.js';
10
+ import { toMaterialAsset } from '../bridge.js';
11
+ import type { GltfMaterialIr } from '../parse-gltf.js';
12
+
13
+ const matIr: GltfMaterialIr = {
14
+ name: 'test-material',
15
+ baseColorFactor: [1, 1, 1, 1],
16
+ metallicFactor: 0,
17
+ roughnessFactor: 0.5,
18
+ };
19
+
20
+ const textureHandles = new Map<number, Handle<'TextureAsset', 'shared'>>();
21
+ const samplerHandles = new Map<number, Handle<'SamplerAsset', 'shared'>>();
22
+
23
+ describe('toMaterialAsset — pass-based type-level', () => {
24
+ it('positive: with undefined ctx compiles', () => {
25
+ const result = toMaterialAsset(matIr, undefined);
26
+ expectTypeOf(result).toMatchTypeOf<ReturnType<typeof toMaterialAsset>>();
27
+ });
28
+
29
+ it('positive: without ctx compiles', () => {
30
+ const result = toMaterialAsset(matIr);
31
+ expectTypeOf(result).toMatchTypeOf<ReturnType<typeof toMaterialAsset>>();
32
+ });
33
+
34
+ it('positive: with both textureHandles and samplerHandles compiles', () => {
35
+ const result = toMaterialAsset(matIr, {
36
+ textureHandles,
37
+ samplerHandles,
38
+ });
39
+ expectTypeOf(result).toMatchTypeOf<ReturnType<typeof toMaterialAsset>>();
40
+ });
41
+
42
+ it('positive: with textureHandles only compiles', () => {
43
+ const result = toMaterialAsset(matIr, { textureHandles });
44
+ expectTypeOf(result).toMatchTypeOf<ReturnType<typeof toMaterialAsset>>();
45
+ });
46
+
47
+ it('type-level: MaterialBridgeContext has optional samplerHandles', () => {
48
+ const ctx: MaterialBridgeContext = { textureHandles };
49
+ expectTypeOf(ctx).toMatchTypeOf<MaterialBridgeContext>();
50
+ });
51
+
52
+ it('type-level: toMaterialAsset is callable as a function', () => {
53
+ expectTypeOf(toMaterialAsset).toBeCallableWith(matIr);
54
+ expectTypeOf(toMaterialAsset).toBeCallableWith(matIr, {
55
+ textureHandles,
56
+ samplerHandles,
57
+ });
58
+ });
59
+ });
@@ -0,0 +1,42 @@
1
+ // bridge-skin-shader-route.unit.test.ts (feat-20260611 M4 / w17-a).
2
+ //
3
+ // Covers cooker-side auto-route: toMaterialAsset emits
4
+ // `forgeax::pbr-skin` when MaterialBridgeContext.skinned === true,
5
+ // falls back to `forgeax::default-standard-pbr` otherwise. The
6
+ // gltf-importer is the only call site that has the per-mesh skin
7
+ // presence info; routing here keeps shader choice content-driven
8
+ // without exposing a runtime auto-resolve (Q4) — the runtime
9
+ // SkinMaterialMismatchError fail-fast in render-system-extract is
10
+ // the reverse-direction safety net for misauthored content.
11
+
12
+ import { describe, expect, it } from 'vitest';
13
+ import { toMaterialAsset } from '../bridge.js';
14
+ import type { GltfMaterialIr } from '../parse-gltf.js';
15
+
16
+ function makeMatIr(): GltfMaterialIr {
17
+ return {
18
+ baseColorFactor: [1, 1, 1, 1] as const,
19
+ metallicFactor: 0.0,
20
+ roughnessFactor: 1.0,
21
+ };
22
+ }
23
+
24
+ describe('toMaterialAsset shader routing (w17-a)', () => {
25
+ it('skinned: true -> passes[0].shader === forgeax::pbr-skin', () => {
26
+ const mat = makeMatIr();
27
+ const asset = toMaterialAsset(mat, { skinned: true });
28
+ expect(asset.passes?.[0]?.program.module).toBe('forgeax::pbr-skin');
29
+ });
30
+
31
+ it('skinned: false -> passes[0].shader === forgeax::default-standard-pbr', () => {
32
+ const mat = makeMatIr();
33
+ const asset = toMaterialAsset(mat, { skinned: false });
34
+ expect(asset.passes?.[0]?.program.module).toBe('forgeax::default-standard-pbr');
35
+ });
36
+
37
+ it('no flag (back-compat) -> passes[0].shader === forgeax::default-standard-pbr', () => {
38
+ const mat = makeMatIr();
39
+ const asset = toMaterialAsset(mat);
40
+ expect(asset.passes?.[0]?.program.module).toBe('forgeax::default-standard-pbr');
41
+ });
42
+ });
@@ -0,0 +1,115 @@
1
+ // bridge-skin-stride.unit.test.ts (feat-20260611 M2 / w9 + w10).
2
+ //
3
+ // Covers AC-08: meshIrToMeshAsset per-MeshAsset stride decision.
4
+ // - w9: skinned + unskinned primitives sharing one glTF mesh both walk the
5
+ // 18-float stride; unskinned primitive's skin slots are zero-filled.
6
+ // - w10: a single unskinned primitive stays on the legacy 12-float stride
7
+ // (OOS-9 / AC-04 — learn-render unskinned demos must not regress).
8
+
9
+ import { describe, expect, it } from 'vitest';
10
+ import type { GltfMeshIr } from '../parse-gltf.js';
11
+ import { unwrapMeshAsset as meshIrToMeshAsset } from './bridge-test-helpers.js';
12
+
13
+ function makeUnskinnedPrim(meshIndex: number, vertexCount: number): GltfMeshIr {
14
+ return {
15
+ meshIndex,
16
+ materialIndex: null,
17
+ positions: new Float32Array(vertexCount * 3),
18
+ indices: new Uint16Array(Array.from({ length: vertexCount }, (_, i) => i)),
19
+ };
20
+ }
21
+
22
+ function makeSkinnedPrim(meshIndex: number, vertexCount: number): GltfMeshIr {
23
+ const joints0 = new Uint16Array(vertexCount * 4);
24
+ const weights0 = new Float32Array(vertexCount * 4);
25
+ for (let v = 0; v < vertexCount; v++) {
26
+ joints0[v * 4 + 0] = v;
27
+ joints0[v * 4 + 1] = v + 1;
28
+ joints0[v * 4 + 2] = v + 2;
29
+ joints0[v * 4 + 3] = v + 3;
30
+ weights0[v * 4 + 0] = 0.4;
31
+ weights0[v * 4 + 1] = 0.3;
32
+ weights0[v * 4 + 2] = 0.2;
33
+ weights0[v * 4 + 3] = 0.1;
34
+ }
35
+ return {
36
+ meshIndex,
37
+ materialIndex: null,
38
+ positions: new Float32Array(vertexCount * 3),
39
+ indices: new Uint16Array(Array.from({ length: vertexCount }, (_, i) => i)),
40
+ joints0,
41
+ weights0,
42
+ };
43
+ }
44
+
45
+ describe('meshIrToMeshAsset stride decision (AC-08)', () => {
46
+ it('skinned + unskinned primitives co-located -> 18F unified, unskinned skin slots zero-filled', () => {
47
+ const skinned = makeSkinnedPrim(0, 3);
48
+ const unskinned = makeUnskinnedPrim(0, 2);
49
+ const asset = meshIrToMeshAsset([skinned, unskinned]);
50
+
51
+ const totalVerts = 3 + 2;
52
+ expect(asset.vertices.length / 18).toBe(totalVerts);
53
+ expect(asset.attributes.skinIndex).toBeInstanceOf(Uint16Array);
54
+ expect(asset.attributes.skinWeight).toBeInstanceOf(Float32Array);
55
+
56
+ const stride = 18;
57
+ // Layout per deriveVertexBufferLayout (72-byte stride):
58
+ // pos[0..2] norm[3..5] uv[6..7] tangent[8..11]
59
+ // skinIndex (uint16x4) at float[12..13] (8 bytes)
60
+ // skinWeight (float32x4) at float[14..17] (16 bytes)
61
+ const interleavedU16 = new Uint16Array(asset.vertices.buffer);
62
+ for (let v = 0; v < 3; v++) {
63
+ const u16Base = (v * stride + 12) * 2;
64
+ expect(interleavedU16[u16Base + 0]).toBe(v);
65
+ expect(interleavedU16[u16Base + 1]).toBe(v + 1);
66
+ expect(interleavedU16[u16Base + 2]).toBe(v + 2);
67
+ expect(interleavedU16[u16Base + 3]).toBe(v + 3);
68
+ expect(asset.vertices[v * stride + 14]).toBeCloseTo(0.4, 5);
69
+ expect(asset.vertices[v * stride + 15]).toBeCloseTo(0.3, 5);
70
+ expect(asset.vertices[v * stride + 16]).toBeCloseTo(0.2, 5);
71
+ expect(asset.vertices[v * stride + 17]).toBeCloseTo(0.1, 5);
72
+ }
73
+ // Unskinned primitive (vertex 3..4) skin slots must be all zero.
74
+ for (let v = 3; v < totalVerts; v++) {
75
+ const u16Base = (v * stride + 12) * 2;
76
+ expect(interleavedU16[u16Base + 0]).toBe(0);
77
+ expect(interleavedU16[u16Base + 1]).toBe(0);
78
+ expect(interleavedU16[u16Base + 2]).toBe(0);
79
+ expect(interleavedU16[u16Base + 3]).toBe(0);
80
+ expect(asset.vertices[v * stride + 14]).toBe(0);
81
+ expect(asset.vertices[v * stride + 15]).toBe(0);
82
+ expect(asset.vertices[v * stride + 16]).toBe(0);
83
+ expect(asset.vertices[v * stride + 17]).toBe(0);
84
+ }
85
+ });
86
+ });
87
+
88
+ describe('meshIrToMeshAsset pure-unskinned path (AC-04 / AC-08 / OOS-9)', () => {
89
+ it('all primitives unskinned -> 12F stride preserved, no skinIndex/skinWeight in attributes', () => {
90
+ const unskinned = makeUnskinnedPrim(0, 4);
91
+ const asset = meshIrToMeshAsset([unskinned]);
92
+
93
+ expect(asset.vertices.length / 12).toBe(4);
94
+ expect(asset.attributes.skinIndex).toBeUndefined();
95
+ expect(asset.attributes.skinWeight).toBeUndefined();
96
+ // Position / normal / uv / tangent attributes still populated.
97
+ expect(asset.attributes.position).toBeInstanceOf(Float32Array);
98
+ expect(asset.attributes.normal).toBeInstanceOf(Float32Array);
99
+ expect(asset.attributes.uv).toBeInstanceOf(Float32Array);
100
+ expect(asset.attributes.tangent).toBeInstanceOf(Float32Array);
101
+ });
102
+ });
103
+
104
+ describe('glTF bridge uses geometry-owned packing', () => {
105
+ it('does not own a parallel 12F/18F stride or uv count calculation', () => {
106
+ const asset = meshIrToMeshAsset([makeUnskinnedPrim(0, 4)]);
107
+ expect(asset.vertices.length / 12).toBe(4);
108
+ expect(asset.attributes.position).toBeInstanceOf(Float32Array);
109
+ });
110
+
111
+ it('keeps no-color producer output free of a synthetic color stream', () => {
112
+ const asset = meshIrToMeshAsset([makeUnskinnedPrim(0, 4)]);
113
+ expect(asset.attributes.color).toBeUndefined();
114
+ });
115
+ });
@@ -0,0 +1,9 @@
1
+ import type { MeshAsset } from '@forgeax/engine-types';
2
+ import { meshIrToMeshAsset } from '../bridge.js';
3
+
4
+ /** Test-only unwrap for fixtures whose validity is the subject under test. */
5
+ export function unwrapMeshAsset(...args: Parameters<typeof meshIrToMeshAsset>): MeshAsset {
6
+ const result = meshIrToMeshAsset(...args);
7
+ if (!result.ok) throw result.error;
8
+ return result.value;
9
+ }