@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
package/src/errors.ts ADDED
@@ -0,0 +1,386 @@
1
+ // errors.ts - GltfError definitions SSOT + factory.
2
+ //
3
+ // Per requirements AC-28 + plan-strategy D-5 (DIP), GltfErrorCode +
4
+ // GltfErrorDetail + GltfError + GLTF_ERROR_HINTS are the glTF importer's
5
+ // own error SSOT, local to this package. They were migrated here from
6
+ // @forgeax/engine-types in feat-20260615-fbx-importer-via-sdk M1 (t7).
7
+ //
8
+ // Producers MUST go through `gltfErr` so any GltfErrorCode addition that
9
+ // lacks a matching detail variant fails at the call site (TS exhaustive
10
+ // per-arm).
11
+ //
12
+ // Result<T, E> + ok / err live in `@forgeax/engine-types` (tweak-20260612-result-
13
+ // into-types) and are re-exported for ergonomic single-import from this module.
14
+
15
+ export {
16
+ createMaterialError,
17
+ err,
18
+ type GltfMaterialUvSetMissingDetail,
19
+ type MaterialError,
20
+ ok,
21
+ type Result,
22
+ } from '@forgeax/engine-types';
23
+
24
+ import type { VertexAttributePackDetail } from '@forgeax/engine-types';
25
+
26
+ // === Meshopt vocabulary owner ===
27
+
28
+ export const GLTF_MESHOPT_MODES = ['ATTRIBUTES', 'TRIANGLES', 'INDICES'] as const;
29
+ export type GltfMeshoptMode = (typeof GLTF_MESHOPT_MODES)[number];
30
+
31
+ export const GLTF_MESHOPT_FILTERS = ['NONE', 'OCTAHEDRAL', 'QUATERNION', 'EXPONENTIAL'] as const;
32
+ export type GltfMeshoptFilter = (typeof GLTF_MESHOPT_FILTERS)[number];
33
+
34
+ // === Per-code detail shapes (22 interfaces, 1 discriminated union) ===
35
+
36
+ /** `gltf-malformed-header` payload: GLB magic / chunk header surface. */
37
+ export interface GltfMalformedHeaderDetail {
38
+ readonly filePath: string;
39
+ readonly byteOffset: number;
40
+ readonly magic?: number;
41
+ }
42
+
43
+ /** `gltf-version-unsupported` payload: surfaced asset.version literal. */
44
+ export interface GltfVersionUnsupportedDetail {
45
+ readonly filePath: string;
46
+ readonly actualVersion: string;
47
+ }
48
+
49
+ /** `gltf-buffer-out-of-bounds` payload: accessor + bufferView coordinates. */
50
+ export interface GltfBufferOutOfBoundsDetail {
51
+ readonly accessor: number;
52
+ readonly byteOffset: number;
53
+ readonly byteLength: number;
54
+ readonly bufferIndex: number;
55
+ }
56
+
57
+ /** `gltf-extension-unsupported` payload: extension name + which array it appeared in. */
58
+ export interface GltfExtensionUnsupportedDetail {
59
+ readonly extension: string;
60
+ readonly source: 'extensionsRequired' | 'extensionsUsed';
61
+ }
62
+
63
+ /** `gltf-accessor-type-mismatch` payload: 4-member closed reason discriminator. */
64
+ export interface GltfAccessorTypeMismatchDetail {
65
+ readonly accessorIndex: number;
66
+ readonly reason: 'sparse' | 'morph' | 'interleaved' | 'unknownComponentType';
67
+ }
68
+
69
+ /** `gltf-texture-load-failed` payload: URI that failed to load. */
70
+ export interface GltfTextureLoadFailedDetail {
71
+ readonly uri: string;
72
+ }
73
+
74
+ /** `gltf-meta-missing` payload: source path + expected sidecar path. */
75
+ export interface GltfMetaMissingDetail {
76
+ readonly filePath: string;
77
+ readonly expectedMetaPath: string;
78
+ }
79
+
80
+ /** `gltf-image-mime-unsupported` payload: rejected MIME type. */
81
+ export interface GltfImageMimeUnsupportedDetail {
82
+ readonly mimeType: string;
83
+ }
84
+
85
+ /** `gltf-skin-joint-count-exceeded` payload: skin joint count exceeds MAX_JOINTS. */
86
+ export interface GltfSkinJointCountExceededDetail {
87
+ readonly skinIndex: number;
88
+ readonly jointCount: number;
89
+ readonly maxJoints: number;
90
+ }
91
+
92
+ /** `gltf-animation-cubicspline-unsupported` payload: CUBICSPLINE sampler. */
93
+ export interface GltfAnimationCubicsplineUnsupportedDetail {
94
+ readonly animationIndex: number;
95
+ readonly samplerIndex: number;
96
+ }
97
+
98
+ /** `gltf-morph-unsupported` payload: channel targeting morph weights. */
99
+ export interface GltfMorphUnsupportedDetail {
100
+ readonly animationIndex: number;
101
+ readonly channelIndex: number;
102
+ readonly nodeIndex: number;
103
+ }
104
+
105
+ /** `gltf-skin-joint-name-missing` payload: joint node has no name. */
106
+ export interface GltfSkinJointNameMissingDetail {
107
+ readonly reason: 'name-missing' | 'hierarchy-cycle';
108
+ readonly skinIndex: number;
109
+ readonly jointPathIndex: number;
110
+ readonly nodeIndex: number;
111
+ }
112
+
113
+ /** `gltf-image-extract-failed` payload: image bytes extraction failure. */
114
+ export interface GltfImageExtractFailedDetail {
115
+ readonly imageIndex: number;
116
+ readonly source: 'bufferView' | 'data-uri' | 'external-uri';
117
+ readonly reason: string;
118
+ }
119
+
120
+ /** `gltf-instancing-count-mismatch` payload: TRS accessor count disagreement. */
121
+ export interface GltfInstancingCountMismatchDetail {
122
+ readonly nodeIndex: number;
123
+ readonly accessor: 'TRANSLATION' | 'ROTATION' | 'SCALE';
124
+ readonly expectedCount: number;
125
+ readonly actualCount: number;
126
+ }
127
+
128
+ /** `gltf-skin-attr-asymmetric` payload: JOINTS_0/WEIGHTS_0 paired-presence fail. */
129
+ export interface GltfSkinAttrAsymmetricDetail {
130
+ readonly meshIndex: number;
131
+ readonly primitiveIndex: number;
132
+ readonly hasJoints: boolean;
133
+ readonly hasWeights: boolean;
134
+ }
135
+
136
+ export interface GltfAnimationTargetInvalidDetail {
137
+ readonly reason:
138
+ | 'name-missing'
139
+ | 'path-invalid'
140
+ | 'path-duplicate'
141
+ | 'path-not-found'
142
+ | 'hierarchy-cycle'
143
+ | 'id-collision';
144
+ readonly animationIndex: number;
145
+ readonly channelIndex: number;
146
+ readonly nodeIndex: number;
147
+ }
148
+
149
+ export interface GltfMeshoptDecoderRequiredDetail {
150
+ readonly bufferView: number;
151
+ readonly actual: 'required' | 'compressed-only';
152
+ readonly hasCoreFallback: boolean;
153
+ }
154
+
155
+ export interface GltfMeshoptDecodeFailedDetail {
156
+ readonly bufferView: number;
157
+ readonly actual: string;
158
+ readonly mode: GltfMeshoptMode;
159
+ readonly filter: GltfMeshoptFilter;
160
+ }
161
+
162
+ export interface GltfMorphInvalidDetail {
163
+ readonly meshIndex: number;
164
+ readonly primitiveIndex: number;
165
+ readonly reason:
166
+ | 'target-count-exceeded'
167
+ | 'attribute-count-exceeded'
168
+ | 'attribute-length-mismatch'
169
+ | 'weights-length-mismatch'
170
+ | 'sparse-or-unsupported-accessor';
171
+ readonly targetCount: number;
172
+ readonly attributeCount: number;
173
+ readonly vertexCount: number;
174
+ }
175
+
176
+ export interface GltfColorAccessorUnsupportedDetail {
177
+ readonly semantic: 'COLOR_0';
178
+ readonly accessorIndex: number;
179
+ readonly reason: 'component' | 'type' | 'normalized' | 'sparse' | 'morph';
180
+ readonly expectedType?: string;
181
+ readonly expectedComponent?: string;
182
+ readonly expectedNormalized?: boolean;
183
+ }
184
+
185
+ export interface GltfColorAccessorMalformedDetail {
186
+ readonly semantic: 'COLOR_0';
187
+ readonly accessorIndex: number;
188
+ readonly reason: 'count' | 'bounds' | 'finite' | 'range' | 'reference';
189
+ readonly expectedCount?: string;
190
+ readonly expectedRange?: string;
191
+ }
192
+
193
+ /** Bridge-side mesh merge failure after the parser has produced a GltfMeshIr. */
194
+ export type GltfMeshBridgeInvalidDetail =
195
+ | {
196
+ readonly reason: 'empty-input';
197
+ readonly primitiveCount: 0;
198
+ }
199
+ | {
200
+ readonly reason: 'morph-count-mismatch';
201
+ readonly meshIndex: number;
202
+ readonly primitiveIndex: number;
203
+ readonly expectedTargetCount: number;
204
+ readonly actualTargetCount: number;
205
+ }
206
+ | {
207
+ readonly reason: 'color-cardinality';
208
+ readonly semantic: 'COLOR_0';
209
+ readonly meshIndex: number;
210
+ readonly primitiveIndex: number;
211
+ readonly vertexCount: number;
212
+ readonly expectedLength: number;
213
+ readonly actualLength: number;
214
+ }
215
+ | {
216
+ readonly reason: 'layout-invalid';
217
+ readonly meshIndex: number;
218
+ readonly cause: VertexAttributePackDetail;
219
+ };
220
+
221
+ /** Discriminated detail family unifying all 19 GltfError variants. */
222
+ export type GltfErrorDetail = DetailFor[GltfErrorCode];
223
+
224
+ // === GltfErrorCode and GltfError discriminated union ===
225
+
226
+ export type GltfErrorCode = keyof DetailFor;
227
+
228
+ export type GltfError = {
229
+ readonly [C in GltfErrorCode]: {
230
+ readonly code: C;
231
+ readonly expected: string;
232
+ readonly hint: string;
233
+ readonly detail: DetailFor[C];
234
+ };
235
+ }[GltfErrorCode];
236
+
237
+ // === Private per-code policy owner ===
238
+
239
+ type GltfErrorPolicy = { readonly expected: string; readonly hint: string };
240
+
241
+ const gltfErrorPolicy = {
242
+ 'gltf-malformed-header': {
243
+ expected:
244
+ 'GLB 12-byte header (magic 0x46546C67 + version=2 + length) plus mandatory JSON chunk',
245
+ hint: 'verify .glb is not truncated; rerun: forgeax-engine-remote-gltf import <path>',
246
+ },
247
+ 'gltf-version-unsupported': {
248
+ expected: 'asset.version === "2.0"',
249
+ hint: 'asset.version must be "2.0"; v1 or v3 not supported',
250
+ },
251
+ 'gltf-buffer-out-of-bounds': {
252
+ expected: 'accessor byte range within bufferView.byteLength',
253
+ hint: 'rebuild .gltf with valid bufferViews; check accessor index; ensure accessor.byteOffset + EFFECTIVE_STRIDE * (count - 1) + element_size <= bufferView.byteLength',
254
+ },
255
+ 'gltf-extension-unsupported': {
256
+ expected: 'extension listed in v1 allowlist (see EXTENSION_ALLOWLIST in @forgeax/engine-gltf)',
257
+ hint: 'see feat-future-gltf-extensions-allowlist; remove this extension or wait for the allowlist to expand',
258
+ },
259
+ 'gltf-accessor-type-mismatch': {
260
+ expected: 'dense fixed-stride accessor with supported componentType',
261
+ hint: 'sparse: see feat-future-gltf-sparse-accessor; morph: see feat-future-gltf-morph; interleaved: see feat-future-gltf-mesh-multi-section',
262
+ },
263
+ 'gltf-texture-load-failed': {
264
+ expected: 'externalLoader resolved the URI into an ArrayBuffer without throwing',
265
+ hint: 'check sidecar meta.json + textures/ directory + vite-plugin-pack /__pack/lookup route',
266
+ },
267
+ 'gltf-meta-missing': {
268
+ expected: "sidecar <source>.meta.json (importer: 'gltf') present in same directory",
269
+ hint: 'run: forgeax-engine-remote-gltf import <path>',
270
+ },
271
+ 'gltf-instancing-count-mismatch': {
272
+ expected: 'all instance attribute accessors share the same count',
273
+ hint: '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',
274
+ },
275
+ 'gltf-image-mime-unsupported': {
276
+ expected: 'image/mimeType is image/jpeg or image/png',
277
+ hint: 'convert to JPG/PNG via external tool; only image/jpeg and image/png are supported',
278
+ },
279
+ 'gltf-skin-joint-count-exceeded': {
280
+ expected: 'skin.joints.length <= MAX_JOINTS (256)',
281
+ hint: 'reduce joint count below MAX_JOINTS (256) or see OOS-skin-max-joints',
282
+ },
283
+ 'gltf-animation-cubicspline-unsupported': {
284
+ expected: 'animation sampler interpolation is LINEAR or STEP',
285
+ hint: 'see OOS-skin-cubicspline; convert CUBICSPLINE to LINEAR/STEP in DCC tool',
286
+ },
287
+ 'gltf-morph-unsupported': {
288
+ expected: 'animation channel target path is one of translation, rotation, scale, or weights',
289
+ hint: 'animation target path must be translation, rotation, scale, or weights',
290
+ },
291
+ 'gltf-skin-joint-name-missing': {
292
+ expected: 'every joint node has a non-empty name and belongs to an acyclic hierarchy',
293
+ hint: 'ensure every joint node has a non-empty name and the node hierarchy is acyclic',
294
+ },
295
+ 'gltf-image-extract-failed': {
296
+ expected:
297
+ 'image bytes extractable from bufferView / data-URI / external URI without corruption',
298
+ hint: '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>',
299
+ },
300
+ 'gltf-skin-attr-asymmetric': {
301
+ expected:
302
+ 'mesh primitive declares JOINTS_0 and WEIGHTS_0 symmetrically (both present or both absent)',
303
+ hint: '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',
304
+ },
305
+ 'gltf-animation-target-invalid': {
306
+ expected:
307
+ 'every animation channel resolves to one uniquely named scene node and stable target ID',
308
+ hint: 'name every node in the animated hierarchy and ensure each animated full path is unique',
309
+ },
310
+ 'gltf-meshopt-decoder-required': {
311
+ expected:
312
+ 'a required or compressed-only EXT_meshopt_compression bufferView has a ready decoder capability',
313
+ hint: 'provide the build-only EXT_meshopt_compression decoder or author a valid core fallback bufferView',
314
+ },
315
+ 'gltf-meshopt-decode-failed': {
316
+ expected: 'the EXT_meshopt_compression declaration and decoder output are structurally valid',
317
+ hint: 'the meshopt decoder accepts the declared compressed range and produces the declared byte count',
318
+ },
319
+ 'gltf-morph-invalid': {
320
+ expected:
321
+ 'morph target and default-weight arrays are dense, bounded, and match the base vertex count',
322
+ hint: 're-export dense morph targets with at most eight targets/attributes and matching vertex/default-weight lengths',
323
+ },
324
+ 'gltf-color-accessor-unsupported': {
325
+ expected: 'COLOR_0 dense accessor uses VEC3/VEC4 FLOAT or normalized UBYTE/USHORT',
326
+ hint: 're-export COLOR_0 with a supported type/component/normalized combination; morph and sparse COLOR_0 remain deferred',
327
+ },
328
+ 'gltf-color-accessor-malformed': {
329
+ expected: 'COLOR_0 accessor is non-empty, finite, in range, and fully addressable',
330
+ hint: 'repair the COLOR_0 accessor count, reference, range, or buffer bounds, then re-import the glTF source',
331
+ },
332
+ 'gltf-mesh-bridge-invalid': {
333
+ expected: 'a non-empty merged mesh with consistent morph and COLOR_0 cardinality',
334
+ hint: 'repair the source primitive and re-import; inspect detail.reason and its typed facts',
335
+ },
336
+ } satisfies Record<GltfErrorCode, GltfErrorPolicy>;
337
+
338
+ export const GLTF_ERROR_HINTS: Readonly<Record<GltfErrorCode, string>> = Object.fromEntries(
339
+ Object.entries(gltfErrorPolicy).map(([code, policy]) => [code, policy.hint]),
340
+ ) as Readonly<Record<GltfErrorCode, string>>;
341
+
342
+ // === DetailFor map + gltfErr factory ===
343
+
344
+ interface DetailFor {
345
+ readonly 'gltf-malformed-header': GltfMalformedHeaderDetail;
346
+ readonly 'gltf-version-unsupported': GltfVersionUnsupportedDetail;
347
+ readonly 'gltf-buffer-out-of-bounds': GltfBufferOutOfBoundsDetail;
348
+ readonly 'gltf-extension-unsupported': GltfExtensionUnsupportedDetail;
349
+ readonly 'gltf-accessor-type-mismatch': GltfAccessorTypeMismatchDetail;
350
+ readonly 'gltf-texture-load-failed': GltfTextureLoadFailedDetail;
351
+ readonly 'gltf-meta-missing': GltfMetaMissingDetail;
352
+ readonly 'gltf-instancing-count-mismatch': GltfInstancingCountMismatchDetail;
353
+ readonly 'gltf-image-mime-unsupported': GltfImageMimeUnsupportedDetail;
354
+ readonly 'gltf-skin-joint-count-exceeded': GltfSkinJointCountExceededDetail;
355
+ readonly 'gltf-animation-cubicspline-unsupported': GltfAnimationCubicsplineUnsupportedDetail;
356
+ readonly 'gltf-morph-unsupported': GltfMorphUnsupportedDetail;
357
+ readonly 'gltf-skin-joint-name-missing': GltfSkinJointNameMissingDetail;
358
+ readonly 'gltf-image-extract-failed': GltfImageExtractFailedDetail;
359
+ readonly 'gltf-skin-attr-asymmetric': GltfSkinAttrAsymmetricDetail;
360
+ readonly 'gltf-animation-target-invalid': GltfAnimationTargetInvalidDetail;
361
+ readonly 'gltf-meshopt-decoder-required': GltfMeshoptDecoderRequiredDetail;
362
+ readonly 'gltf-meshopt-decode-failed': GltfMeshoptDecodeFailedDetail;
363
+ readonly 'gltf-morph-invalid': GltfMorphInvalidDetail;
364
+ readonly 'gltf-color-accessor-unsupported': GltfColorAccessorUnsupportedDetail;
365
+ readonly 'gltf-color-accessor-malformed': GltfColorAccessorMalformedDetail;
366
+ readonly 'gltf-mesh-bridge-invalid': GltfMeshBridgeInvalidDetail;
367
+ }
368
+
369
+ /**
370
+ * Build a fully-typed GltfError. The discriminated-union return type lets
371
+ * call sites narrow with `switch (e.code)` on the result.
372
+ *
373
+ * Charter proposition 4 explicit-failure: `expected` + `hint` fields are
374
+ * sourced from the SSOT tables - no producer can omit them.
375
+ */
376
+ export function gltfErr<C extends GltfErrorCode>(
377
+ code: C,
378
+ detail: DetailFor[C],
379
+ ): Extract<GltfError, { readonly code: C }> {
380
+ return {
381
+ code,
382
+ expected: gltfErrorPolicy[code].expected,
383
+ hint: gltfErrorPolicy[code].hint,
384
+ detail,
385
+ } as Extract<GltfError, { readonly code: C }>;
386
+ }