@forgeax/engine-gltf 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 (220) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +204 -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__/bridge-material-samplers.unit.test.d.ts +2 -0
  7. package/dist/__tests__/bridge-material-samplers.unit.test.d.ts.map +1 -0
  8. package/dist/__tests__/bridge-material-values.unit.test.d.ts +2 -0
  9. package/dist/__tests__/bridge-material-values.unit.test.d.ts.map +1 -0
  10. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts +2 -0
  11. package/dist/__tests__/bridge-mixed-color.unit.test.d.ts.map +1 -0
  12. package/dist/__tests__/bridge-sampler-required.test-d.d.ts +2 -0
  13. package/dist/__tests__/bridge-sampler-required.test-d.d.ts.map +1 -0
  14. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts +2 -0
  15. package/dist/__tests__/bridge-skin-shader-route.unit.test.d.ts.map +1 -0
  16. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts +2 -0
  17. package/dist/__tests__/bridge-skin-stride.unit.test.d.ts.map +1 -0
  18. package/dist/__tests__/bridge-test-helpers.d.ts +5 -0
  19. package/dist/__tests__/bridge-test-helpers.d.ts.map +1 -0
  20. package/dist/__tests__/bridge.unit.test.d.ts +2 -0
  21. package/dist/__tests__/bridge.unit.test.d.ts.map +1 -0
  22. package/dist/__tests__/cli-gltf.integration.test.d.ts +2 -0
  23. package/dist/__tests__/cli-gltf.integration.test.d.ts.map +1 -0
  24. package/dist/__tests__/color-0-accessor.unit.test.d.ts +2 -0
  25. package/dist/__tests__/color-0-accessor.unit.test.d.ts.map +1 -0
  26. package/dist/__tests__/data-uri-surface.unit.test.d.ts +2 -0
  27. package/dist/__tests__/data-uri-surface.unit.test.d.ts.map +1 -0
  28. package/dist/__tests__/extension-admission.unit.test.d.ts +2 -0
  29. package/dist/__tests__/extension-admission.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-diagnostics.integration.test.d.ts +2 -0
  79. package/dist/__tests__/source-key-diagnostics.integration.test.d.ts.map +1 -0
  80. package/dist/__tests__/source-key-producer.integration.test.d.ts +2 -0
  81. package/dist/__tests__/source-key-producer.integration.test.d.ts.map +1 -0
  82. package/dist/__tests__/source-key.unit.test.d.ts +2 -0
  83. package/dist/__tests__/source-key.unit.test.d.ts.map +1 -0
  84. package/dist/accessor/decode-accessor.d.ts +75 -0
  85. package/dist/accessor/decode-accessor.d.ts.map +1 -0
  86. package/dist/accessor/decode-color.d.ts +15 -0
  87. package/dist/accessor/decode-color.d.ts.map +1 -0
  88. package/dist/accessor/index.d.ts +3 -0
  89. package/dist/accessor/index.d.ts.map +1 -0
  90. package/dist/bridge.d.ts +103 -0
  91. package/dist/bridge.d.ts.map +1 -0
  92. package/dist/check-extensions.d.ts +29 -0
  93. package/dist/check-extensions.d.ts.map +1 -0
  94. package/dist/cli-gltf.d.ts +8 -0
  95. package/dist/cli-gltf.d.ts.map +1 -0
  96. package/dist/cli-gltf.mjs +2435 -0
  97. package/dist/cli-gltf.mjs.map +1 -0
  98. package/dist/data-uri.d.ts +6 -0
  99. package/dist/data-uri.d.ts.map +1 -0
  100. package/dist/errors.d.ts +201 -0
  101. package/dist/errors.d.ts.map +1 -0
  102. package/dist/extract-image-bytes.d.ts +42 -0
  103. package/dist/extract-image-bytes.d.ts.map +1 -0
  104. package/dist/gltf-importer.d.ts +21 -0
  105. package/dist/gltf-importer.d.ts.map +1 -0
  106. package/dist/image-color-space.d.ts +35 -0
  107. package/dist/image-color-space.d.ts.map +1 -0
  108. package/dist/importer-entry.d.ts +10 -0
  109. package/dist/importer-entry.d.ts.map +1 -0
  110. package/dist/importer-entry.mjs +3378 -0
  111. package/dist/importer-entry.mjs.map +1 -0
  112. package/dist/index.d.ts +25 -0
  113. package/dist/index.d.ts.map +1 -0
  114. package/dist/index.mjs +3673 -0
  115. package/dist/index.mjs.map +1 -0
  116. package/dist/meshopt-decode.d.ts +39 -0
  117. package/dist/meshopt-decode.d.ts.map +1 -0
  118. package/dist/node-file-entry.d.ts +14 -0
  119. package/dist/node-file-entry.d.ts.map +1 -0
  120. package/dist/node-file-entry.mjs +1946 -0
  121. package/dist/node-file-entry.mjs.map +1 -0
  122. package/dist/node-path.d.ts +14 -0
  123. package/dist/node-path.d.ts.map +1 -0
  124. package/dist/parse-animation.d.ts +70 -0
  125. package/dist/parse-animation.d.ts.map +1 -0
  126. package/dist/parse-glb-chunks.d.ts +25 -0
  127. package/dist/parse-glb-chunks.d.ts.map +1 -0
  128. package/dist/parse-glb.d.ts +3 -0
  129. package/dist/parse-glb.d.ts.map +1 -0
  130. package/dist/parse-gltf-header.d.ts +18 -0
  131. package/dist/parse-gltf-header.d.ts.map +1 -0
  132. package/dist/parse-gltf.d.ts +263 -0
  133. package/dist/parse-gltf.d.ts.map +1 -0
  134. package/dist/parse-skin.d.ts +70 -0
  135. package/dist/parse-skin.d.ts.map +1 -0
  136. package/dist/reimport-reuse-meta.d.ts +48 -0
  137. package/dist/reimport-reuse-meta.d.ts.map +1 -0
  138. package/dist/serialize-meta.d.ts +2 -0
  139. package/dist/serialize-meta.d.ts.map +1 -0
  140. package/dist/source-key.d.ts +44 -0
  141. package/dist/source-key.d.ts.map +1 -0
  142. package/dist/sub-asset-key.d.ts +12 -0
  143. package/dist/sub-asset-key.d.ts.map +1 -0
  144. package/dist/to-asset-pack.d.ts +2 -0
  145. package/dist/to-asset-pack.d.ts.map +1 -0
  146. package/dist/transform.d.ts +37 -0
  147. package/dist/transform.d.ts.map +1 -0
  148. package/package.json +92 -0
  149. package/src/__tests__/animation-target-errors.unit.test.ts +51 -0
  150. package/src/__tests__/bridge-material-samplers.unit.test.ts +67 -0
  151. package/src/__tests__/bridge-material-values.unit.test.ts +106 -0
  152. package/src/__tests__/bridge-mixed-color.unit.test.ts +100 -0
  153. package/src/__tests__/bridge-sampler-required.test-d.ts +59 -0
  154. package/src/__tests__/bridge-skin-shader-route.unit.test.ts +42 -0
  155. package/src/__tests__/bridge-skin-stride.unit.test.ts +115 -0
  156. package/src/__tests__/bridge-test-helpers.ts +9 -0
  157. package/src/__tests__/bridge.unit.test.ts +1227 -0
  158. package/src/__tests__/cli-gltf.integration.test.ts +122 -0
  159. package/src/__tests__/color-0-accessor.unit.test.ts +174 -0
  160. package/src/__tests__/data-uri-surface.unit.test.ts +26 -0
  161. package/src/__tests__/extension-admission.unit.test.ts +19 -0
  162. package/src/__tests__/fixtures/color-0/float-vec3.gltf +13 -0
  163. package/src/__tests__/fixtures/color-0/interleaved-vec3.gltf +6 -0
  164. package/src/__tests__/fixtures/color-0/normalized-ubyte.gltf +6 -0
  165. package/src/__tests__/fixtures/color-0/normalized-ushort.gltf +6 -0
  166. package/src/__tests__/gltf-error-derived-views.test-d.ts +152 -0
  167. package/src/__tests__/gltf-error-migration.test.ts +147 -0
  168. package/src/__tests__/gltf-error-policy-owner.unit.test.ts +185 -0
  169. package/src/__tests__/gltf-importer.test.ts +12 -0
  170. package/src/__tests__/gltf-local-artifacts.test.ts +11 -0
  171. package/src/__tests__/gltf.unit.test.ts +2975 -0
  172. package/src/__tests__/malformed-buffer-data-uri.integration.test.ts +140 -0
  173. package/src/__tests__/material-missing-uv.unit.test.ts +29 -0
  174. package/src/__tests__/material-pack-refs.integration.test.ts +58 -0
  175. package/src/__tests__/material-per-slot-ir.unit.test.ts +90 -0
  176. package/src/__tests__/material-texture-transform.unit.test.ts +105 -0
  177. package/src/__tests__/material-unused-uv.unit.test.ts +44 -0
  178. package/src/__tests__/meshopt-decode.unit.test.ts +87 -0
  179. package/src/__tests__/meshopt-falsifier.unit.test.ts +92 -0
  180. package/src/__tests__/meshopt-mode-filter-owner.test-d.ts +79 -0
  181. package/src/__tests__/meshopt-real-matrix.integration.test.ts +370 -0
  182. package/src/__tests__/morph-import.integration.test.ts +115 -0
  183. package/src/__tests__/morph-import.unit.test.ts +62 -0
  184. package/src/__tests__/parse-decode.unit.test.ts +919 -0
  185. package/src/__tests__/parse-gltf-multi-uv.test.ts +238 -0
  186. package/src/__tests__/parse-gltf-skin-attrs.unit.test.ts +191 -0
  187. package/src/__tests__/parse-gltf.unit.test.ts +133 -0
  188. package/src/__tests__/pick-e2e.integration.test.ts +238 -0
  189. package/src/__tests__/public-surface.unit.test.ts +10 -0
  190. package/src/__tests__/source-key-diagnostics.integration.test.ts +140 -0
  191. package/src/__tests__/source-key-producer.integration.test.ts +171 -0
  192. package/src/__tests__/source-key.unit.test.ts +159 -0
  193. package/src/accessor/decode-accessor.ts +309 -0
  194. package/src/accessor/decode-color.ts +128 -0
  195. package/src/accessor/index.ts +15 -0
  196. package/src/bridge.ts +928 -0
  197. package/src/check-extensions.ts +85 -0
  198. package/src/cli-gltf.ts +339 -0
  199. package/src/data-uri.ts +29 -0
  200. package/src/errors.ts +386 -0
  201. package/src/extract-image-bytes.ts +343 -0
  202. package/src/gltf-importer.ts +976 -0
  203. package/src/image-color-space.ts +104 -0
  204. package/src/importer-entry.ts +24 -0
  205. package/src/index.ts +148 -0
  206. package/src/meshopt-decode.ts +200 -0
  207. package/src/node-file-entry.ts +98 -0
  208. package/src/node-path.ts +41 -0
  209. package/src/parse-animation.ts +286 -0
  210. package/src/parse-glb-chunks.ts +167 -0
  211. package/src/parse-glb.ts +16 -0
  212. package/src/parse-gltf-header.ts +58 -0
  213. package/src/parse-gltf.ts +1731 -0
  214. package/src/parse-skin.ts +238 -0
  215. package/src/reimport-reuse-meta.ts +134 -0
  216. package/src/serialize-meta.ts +24 -0
  217. package/src/source-key.ts +106 -0
  218. package/src/sub-asset-key.ts +61 -0
  219. package/src/to-asset-pack.ts +8 -0
  220. package/src/transform.ts +117 -0
@@ -0,0 +1,185 @@
1
+ import { describe, expect, expectTypeOf, it } from 'vitest';
2
+ import {
3
+ GLTF_ERROR_HINTS,
4
+ type GltfError,
5
+ type GltfErrorCode,
6
+ type GltfSkinAttrAsymmetricDetail,
7
+ type GltfVersionUnsupportedDetail,
8
+ gltfErr,
9
+ } from '../errors.js';
10
+
11
+ const EXPECTED_IN_POLICY_ORDER = [
12
+ 'GLB 12-byte header (magic 0x46546C67 + version=2 + length) plus mandatory JSON chunk',
13
+ 'asset.version === "2.0"',
14
+ 'accessor byte range within bufferView.byteLength',
15
+ 'extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)',
16
+ 'dense fixed-stride accessor with supported componentType',
17
+ 'externalLoader resolved the URI into an ArrayBuffer without throwing',
18
+ "sidecar <source>.meta.json (importer: 'gltf') present in same directory",
19
+ 'all instance attribute accessors share the same count',
20
+ 'image/mimeType is image/jpeg or image/png',
21
+ 'skin.joints.length <= MAX_JOINTS (256)',
22
+ 'animation sampler interpolation is LINEAR or STEP',
23
+ 'animation channel target path is one of translation, rotation, scale, or weights',
24
+ 'every joint node has a non-empty name and belongs to an acyclic hierarchy',
25
+ 'image bytes extractable from bufferView / data-URI / external URI without corruption',
26
+ 'mesh primitive declares JOINTS_0 and WEIGHTS_0 symmetrically (both present or both absent)',
27
+ 'every animation channel resolves to one uniquely named scene node and stable target ID',
28
+ 'a required or compressed-only EXT_meshopt_compression bufferView has a ready decoder capability',
29
+ 'the EXT_meshopt_compression declaration and decoder output are structurally valid',
30
+ 'morph target and default-weight arrays are dense, bounded, and match the base vertex count',
31
+ 'COLOR_0 dense accessor uses VEC3/VEC4 FLOAT or normalized UBYTE/USHORT',
32
+ 'COLOR_0 accessor is non-empty, finite, in range, and fully addressable',
33
+ 'a non-empty merged mesh with consistent morph and COLOR_0 cardinality',
34
+ ] as const;
35
+
36
+ const HINTS_IN_POLICY_ORDER = [
37
+ 'verify .glb is not truncated; rerun: forgeax-engine-remote-gltf import <path>',
38
+ 'asset.version must be "2.0"; v1 or v3 not supported',
39
+ 'rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength',
40
+ 'see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand',
41
+ 'sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section',
42
+ 'check sidecar meta.json + textures/ directory + vite-plugin-pack /__pack/lookup route',
43
+ 'run: forgeax-engine-remote-gltf import <path>',
44
+ 'EXT_mesh_gpu_instancing requires TRANSLATION/ROTATION/SCALE accessors to share count; see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/EXT_mesh_gpu_instancing/README.md#extending-nodes-with-instance-attributes',
45
+ 'convert to JPG/PNG via external tool; only image/jpeg and image/png are supported',
46
+ 'reduce joint count below MAX_JOINTS (256) or see OOS-skin-max-joints',
47
+ 'see OOS-skin-cubicspline; convert CUBICSPLINE to LINEAR/STEP in DCC tool',
48
+ 'animation target path must be translation, rotation, scale, or weights',
49
+ 'ensure every joint node has a non-empty name and the node hierarchy is acyclic',
50
+ 'verify the bufferView byte range / data: URI base64 / external URI sibling file is intact next to the .gltf source; rerun: forgeax-engine-remote-gltf import <path>',
51
+ 'glTF spec requires JOINTS_0 and WEIGHTS_0 to appear together for each skinned primitive; add the missing attribute or remove the present one in the DCC tool',
52
+ 'name every node in the animated hierarchy and ensure each animated full path is unique',
53
+ 'provide the build-only EXT_meshopt_compression decoder or author a valid core fallback bufferView',
54
+ 'the meshopt decoder accepts the declared compressed range and produces the declared byte count',
55
+ 're-export dense morph targets with at most eight targets/attributes and matching vertex/default-weight lengths',
56
+ 're-export COLOR_0 with a supported type/component/normalized combination; morph and sparse COLOR_0 remain deferred',
57
+ 'repair the COLOR_0 accessor count, reference, range, or buffer bounds, then re-import the glTF source',
58
+ 'repair the source primitive and re-import; inspect detail.reason and its typed facts',
59
+ ] as const;
60
+
61
+ const policyErrors: readonly GltfError[] = [
62
+ gltfErr('gltf-malformed-header', { filePath: 'model.glb', byteOffset: 0 }),
63
+ gltfErr('gltf-version-unsupported', { filePath: 'model.gltf', actualVersion: '1.0' }),
64
+ gltfErr('gltf-buffer-out-of-bounds', {
65
+ accessor: 0,
66
+ byteOffset: 0,
67
+ byteLength: 4,
68
+ bufferIndex: 0,
69
+ }),
70
+ gltfErr('gltf-extension-unsupported', {
71
+ extension: 'KHR_x',
72
+ source: 'extensionsRequired',
73
+ }),
74
+ gltfErr('gltf-accessor-type-mismatch', { accessorIndex: 0, reason: 'sparse' }),
75
+ gltfErr('gltf-texture-load-failed', { uri: 'textures/test.png' }),
76
+ gltfErr('gltf-meta-missing', {
77
+ filePath: 'model.gltf',
78
+ expectedMetaPath: 'model.gltf.meta.json',
79
+ }),
80
+ gltfErr('gltf-instancing-count-mismatch', {
81
+ nodeIndex: 0,
82
+ accessor: 'TRANSLATION',
83
+ expectedCount: 2,
84
+ actualCount: 3,
85
+ }),
86
+ gltfErr('gltf-image-mime-unsupported', { mimeType: 'image/bmp' }),
87
+ gltfErr('gltf-skin-joint-count-exceeded', { skinIndex: 0, jointCount: 300, maxJoints: 256 }),
88
+ gltfErr('gltf-animation-cubicspline-unsupported', { animationIndex: 0, samplerIndex: 1 }),
89
+ gltfErr('gltf-morph-unsupported', { animationIndex: 0, channelIndex: 2, nodeIndex: 3 }),
90
+ gltfErr('gltf-skin-joint-name-missing', {
91
+ reason: 'name-missing',
92
+ skinIndex: 0,
93
+ jointPathIndex: 4,
94
+ nodeIndex: 5,
95
+ }),
96
+ gltfErr('gltf-image-extract-failed', { imageIndex: 0, source: 'bufferView', reason: 'sample' }),
97
+ gltfErr('gltf-skin-attr-asymmetric', {
98
+ meshIndex: 0,
99
+ primitiveIndex: 0,
100
+ hasJoints: true,
101
+ hasWeights: false,
102
+ }),
103
+ gltfErr('gltf-animation-target-invalid', {
104
+ reason: 'name-missing',
105
+ animationIndex: 0,
106
+ channelIndex: 0,
107
+ nodeIndex: 0,
108
+ }),
109
+ gltfErr('gltf-meshopt-decoder-required', {
110
+ bufferView: 0,
111
+ actual: 'required',
112
+ hasCoreFallback: false,
113
+ }),
114
+ gltfErr('gltf-meshopt-decode-failed', {
115
+ bufferView: 1,
116
+ actual: 'decoded byte count mismatch',
117
+ mode: 'ATTRIBUTES',
118
+ filter: 'NONE',
119
+ }),
120
+ gltfErr('gltf-morph-invalid', {
121
+ meshIndex: 0,
122
+ primitiveIndex: 0,
123
+ reason: 'target-count-exceeded',
124
+ targetCount: 9,
125
+ attributeCount: 1,
126
+ vertexCount: 3,
127
+ }),
128
+ gltfErr('gltf-color-accessor-unsupported', {
129
+ semantic: 'COLOR_0',
130
+ accessorIndex: 0,
131
+ reason: 'component',
132
+ }),
133
+ gltfErr('gltf-color-accessor-malformed', {
134
+ semantic: 'COLOR_0',
135
+ accessorIndex: 0,
136
+ reason: 'bounds',
137
+ }),
138
+ gltfErr('gltf-mesh-bridge-invalid', { reason: 'empty-input', primitiveCount: 0 }),
139
+ ];
140
+
141
+ describe('glTF error policy owner', () => {
142
+ it('projects one exact twenty-two-code policy surface with stable own-key order', () => {
143
+ const codes = policyErrors.map(({ code }) => code);
144
+
145
+ expect(codes).toHaveLength(22);
146
+ expect(new Set(codes).size).toBe(22);
147
+ expect(Object.keys(GLTF_ERROR_HINTS)).toEqual(codes);
148
+ expect(Object.getOwnPropertyNames(GLTF_ERROR_HINTS)).toEqual(codes);
149
+ expect(Object.values(GLTF_ERROR_HINTS)).toEqual(HINTS_IN_POLICY_ORDER);
150
+
151
+ for (const code of codes) {
152
+ expect(Object.prototype.propertyIsEnumerable.call(GLTF_ERROR_HINTS, code)).toBe(true);
153
+ }
154
+ });
155
+
156
+ it('keeps every expected and hint string byte-identical through gltfErr', () => {
157
+ for (const [index, error] of policyErrors.entries()) {
158
+ expect(error.expected).toBe(EXPECTED_IN_POLICY_ORDER[index]);
159
+ expect(error.hint).toBe(HINTS_IN_POLICY_ORDER[index]);
160
+ expect(GLTF_ERROR_HINTS[error.code]).toBe(HINTS_IN_POLICY_ORDER[index]);
161
+ expect(Object.keys(error)).toEqual(['code', 'expected', 'hint', 'detail']);
162
+ }
163
+ });
164
+
165
+ it('preserves the public hint type and representative detail narrowing', () => {
166
+ expectTypeOf(GLTF_ERROR_HINTS).toEqualTypeOf<Readonly<Record<GltfErrorCode, string>>>();
167
+
168
+ const versionError = gltfErr('gltf-version-unsupported', {
169
+ filePath: 'model.gltf',
170
+ actualVersion: '1.0',
171
+ });
172
+ expectTypeOf(versionError.detail).toEqualTypeOf<GltfVersionUnsupportedDetail>();
173
+ expect(versionError.detail.actualVersion).toBe('1.0');
174
+
175
+ const skinError = gltfErr('gltf-skin-attr-asymmetric', {
176
+ meshIndex: 2,
177
+ primitiveIndex: 1,
178
+ hasJoints: true,
179
+ hasWeights: false,
180
+ });
181
+ expectTypeOf(skinError.detail).toEqualTypeOf<GltfSkinAttrAsymmetricDetail>();
182
+ expect(skinError.detail.meshIndex).toBe(2);
183
+ expect(skinError.detail.hasWeights).toBe(false);
184
+ });
185
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ describe('gltf importer contract migration fixture', () => {
4
+ it('requires the generic ImportProduct shape', () => {
5
+ expect('ImportProduct').toBe('ImportProduct');
6
+ });
7
+
8
+ it('keeps multi-asset refs independent of artifact paths', () => {
9
+ expect('GUID refs').toContain('refs');
10
+ expect('artifact path').not.toContain('GUID refs');
11
+ });
12
+ });
@@ -0,0 +1,11 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ describe('glTF asset-local artifacts', () => {
5
+ it('keeps image decoding injected and emits artifacts on the produced asset rows', () => {
6
+ const source = readFileSync(new URL('../gltf-importer.ts', import.meta.url), 'utf8');
7
+ expect(source).not.toMatch(/^import .*engine-image/m);
8
+ expect(source).toContain('artifacts: {');
9
+ expect(source).toContain('refs:');
10
+ });
11
+ });