@instruktlabs/kiln 1.2.2 → 1.2.3

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 (214) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +74 -0
  4. package/README.md +7 -0
  5. package/dist/agent-run.mjs +6392 -1477
  6. package/dist/build.json +23 -23
  7. package/dist/cli.mjs +8453 -3407
  8. package/dist/evaluator-worker.mjs +16608 -12387
  9. package/dist/mcp-engine.mjs +6746 -1725
  10. package/dist/mcp-server.mjs +4 -4
  11. package/dist/viewer/app.js +203 -199
  12. package/dist/viewer/chat.html +209 -205
  13. package/dist/viewer/style.css +10 -0
  14. package/docs/atlas-provenance.md +23 -0
  15. package/docs/community-exporter.md +27 -0
  16. package/docs/export-profiles.md +20 -0
  17. package/docs/material-library.md +9 -5
  18. package/docs/procedural-surfaces-v3.md +276 -0
  19. package/docs/projects-and-live-review.md +1 -1
  20. package/docs/rendering.md +41 -0
  21. package/docs/sdk.md +53 -0
  22. package/docs/tools-operations.md +2 -2
  23. package/docs/tools.md +3 -3
  24. package/lib/asset-materials-node.d.ts +3 -3
  25. package/lib/asset-materials-node.js +29 -33
  26. package/lib/assets-node.d.ts +1 -1
  27. package/lib/assets-node.js +11 -4
  28. package/lib/assets.d.ts +4 -2
  29. package/lib/assets.js +61 -16
  30. package/lib/atlas-provenance.d.ts +86 -0
  31. package/lib/atlas-provenance.js +448 -0
  32. package/lib/character.d.ts +11 -2
  33. package/lib/character.js +90 -2
  34. package/lib/community-exporter.d.ts +3 -2
  35. package/lib/community-exporter.js +139 -3
  36. package/lib/discovery/helper-contracts.js +161 -7
  37. package/lib/discovery/helper-specs.js +49 -4
  38. package/lib/discovery/shapes.js +20 -7
  39. package/lib/engine-identity.d.ts +1 -1
  40. package/lib/engine-identity.js +1 -1
  41. package/lib/evaluator/authoring-diagnostic.d.ts +1 -1
  42. package/lib/evaluator/authoring-diagnostic.js +12 -0
  43. package/lib/evaluator/protocol.d.ts +2 -2
  44. package/lib/evaluator/protocol.js +16 -6
  45. package/lib/evaluator/subprocess.js +2 -2
  46. package/lib/exporter-node.js +9 -1
  47. package/lib/geometry-budget.d.ts +7 -0
  48. package/lib/geometry-budget.js +8 -0
  49. package/lib/gltf-texture-binding.d.ts +6 -0
  50. package/lib/gltf-texture-binding.js +54 -0
  51. package/lib/material-authoring-node.d.ts +6 -0
  52. package/lib/material-authoring-node.js +49 -0
  53. package/lib/material-authoring-schema.d.ts +871 -0
  54. package/lib/material-authoring-schema.js +310 -0
  55. package/lib/material-export.d.ts +5 -0
  56. package/lib/material-export.js +21 -0
  57. package/lib/material-library-node.d.ts +34 -8
  58. package/lib/material-library-node.js +691 -11
  59. package/lib/material-library.d.ts +3704 -4
  60. package/lib/material-library.js +272 -0
  61. package/lib/mcp-core.d.ts +1 -1
  62. package/lib/pinned-material-map.d.ts +33 -0
  63. package/lib/pinned-material-map.js +1 -0
  64. package/lib/portable-material-runtime.d.ts +5 -0
  65. package/lib/portable-material-runtime.js +160 -0
  66. package/lib/portable-sampler-runtime.d.ts +4 -0
  67. package/lib/portable-sampler-runtime.js +26 -0
  68. package/lib/portable-sampler.d.ts +11 -0
  69. package/lib/portable-sampler.js +42 -0
  70. package/lib/primitives.d.ts +15 -3
  71. package/lib/primitives.js +24 -4
  72. package/lib/procedural-material-v2.d.ts +2 -0
  73. package/lib/procedural-material-v2.js +2 -1
  74. package/lib/procedural-material-v3.d.ts +72 -0
  75. package/lib/procedural-material-v3.js +162 -0
  76. package/lib/procedural-texture-v2-compiler.d.ts +31 -0
  77. package/lib/procedural-texture-v2-compiler.js +172 -0
  78. package/lib/procedural-texture-v3-contract.d.ts +125 -0
  79. package/lib/procedural-texture-v3-contract.js +333 -0
  80. package/lib/procedural-texture-v3.d.ts +12 -0
  81. package/lib/procedural-texture-v3.js +165 -0
  82. package/lib/procedural-texture.d.ts +9 -31
  83. package/lib/procedural-texture.js +43 -170
  84. package/lib/project-bundle-node.d.ts +1 -1
  85. package/lib/project-bundle-node.js +70 -13
  86. package/lib/project-bundle.d.ts +91 -3
  87. package/lib/project-bundle.js +48 -7
  88. package/lib/qa/atlas.d.ts +21 -0
  89. package/lib/qa/atlas.js +474 -0
  90. package/lib/qa/index.d.ts +1 -0
  91. package/lib/qa/index.js +1 -0
  92. package/lib/qa/material.js +2 -0
  93. package/lib/qa/skin-integrity.d.ts +18 -0
  94. package/lib/qa/skin-integrity.js +221 -0
  95. package/lib/render-service-host.js +4 -7
  96. package/lib/render.d.ts +3 -3
  97. package/lib/render.js +108 -16
  98. package/lib/skin-binding.d.ts +20 -0
  99. package/lib/skin-binding.js +144 -0
  100. package/lib/texture-atlas-contract.d.ts +97 -0
  101. package/lib/texture-atlas-contract.js +414 -0
  102. package/lib/texture-atlas-pixels.d.ts +73 -0
  103. package/lib/texture-atlas-pixels.js +290 -0
  104. package/lib/texture-atlas.d.ts +10 -0
  105. package/lib/texture-atlas.js +143 -0
  106. package/lib/texture-bake.d.ts +3 -0
  107. package/lib/texture-bake.js +66 -11
  108. package/lib/texture-resolver.d.ts +22 -0
  109. package/lib/texture-resolver.js +24 -0
  110. package/lib/tools/materials.js +8 -7
  111. package/lib/tools/registry.js +5 -16
  112. package/lib/tools/review.js +1 -1
  113. package/lib/uv-data.d.ts +32 -0
  114. package/lib/uv-data.js +172 -0
  115. package/lib/uv-shapes.js +3 -0
  116. package/lib/uv.d.ts +9 -3
  117. package/lib/uv.js +53 -82
  118. package/lib/views/camera.d.ts +3 -3
  119. package/lib/views/camera.js +32 -28
  120. package/lib/views/face-policy.d.ts +9 -0
  121. package/lib/views/face-policy.js +16 -0
  122. package/lib/views/glb.js +218 -24
  123. package/lib/views/pose.d.ts +7 -1
  124. package/lib/views/pose.js +63 -12
  125. package/lib/views/raster.d.ts +6 -2
  126. package/lib/views/raster.js +49 -37
  127. package/lib/views/scene-copy.d.ts +6 -0
  128. package/lib/views/scene-copy.js +73 -0
  129. package/lib/views/scene-raster.js +2 -1
  130. package/lib/views/triangle-coverage.d.ts +8 -0
  131. package/lib/views/triangle-coverage.js +58 -0
  132. package/lib/workspace-node.js +7 -3
  133. package/lib/workspace.d.ts +2 -2
  134. package/package.json +3 -1
  135. package/plugin.json +1 -1
  136. package/plugins/kiln-engine/.claude-plugin/plugin.json +1 -1
  137. package/plugins/kiln-engine/README.md +3 -3
  138. package/plugins/kiln-engine/package-provenance.json +6 -6
  139. package/plugins/kiln-engine/plugin.json +1 -1
  140. package/plugins/kiln-engine/runtime.json +1 -1
  141. package/skills/kiln-author-asset/references/geometry-recipes.md +124 -0
  142. package/skills/kiln-author-asset/references/projects-and-materials.md +32 -2
  143. package/skills/kiln-compose-scene/references/runtime-scenes.md +19 -0
  144. package/skills/kiln-qa-asset/references/integration-checks.md +37 -2
  145. package/src/asset-materials-node.ts +38 -45
  146. package/src/assets-node.ts +22 -5
  147. package/src/assets.ts +61 -20
  148. package/src/atlas-provenance.ts +522 -0
  149. package/src/character.ts +107 -2
  150. package/src/community-exporter.ts +143 -3
  151. package/src/discovery/helper-contracts.ts +188 -7
  152. package/src/discovery/helper-specs.ts +69 -4
  153. package/src/discovery/shapes.ts +25 -9
  154. package/src/engine-identity.ts +1 -1
  155. package/src/evaluator/authoring-diagnostic.ts +19 -1
  156. package/src/evaluator/protocol.ts +19 -9
  157. package/src/evaluator/subprocess.ts +2 -2
  158. package/src/exporter-node.ts +12 -1
  159. package/src/generated/mcp-manifest.json +3 -3
  160. package/src/geometry-budget.ts +8 -0
  161. package/src/gltf-texture-binding.ts +60 -0
  162. package/src/material-authoring-node.ts +68 -0
  163. package/src/material-authoring-schema.ts +343 -0
  164. package/src/material-export.ts +25 -0
  165. package/src/material-library-node.ts +840 -16
  166. package/src/material-library.ts +348 -5
  167. package/src/pinned-material-map.ts +43 -0
  168. package/src/portable-material-runtime.ts +200 -0
  169. package/src/portable-sampler-runtime.ts +40 -0
  170. package/src/portable-sampler.ts +54 -0
  171. package/src/primitives.ts +98 -4
  172. package/src/procedural-material-v2.ts +2 -1
  173. package/src/procedural-material-v3.ts +224 -0
  174. package/src/procedural-texture-v2-compiler.ts +234 -0
  175. package/src/procedural-texture-v3-contract.ts +437 -0
  176. package/src/procedural-texture-v3.ts +220 -0
  177. package/src/procedural-texture.ts +61 -229
  178. package/src/project-bundle-node.ts +106 -14
  179. package/src/project-bundle.ts +61 -18
  180. package/src/project-cli.ts +29 -17
  181. package/src/qa/atlas.ts +597 -0
  182. package/src/qa/index.ts +1 -0
  183. package/src/qa/material.ts +2 -0
  184. package/src/qa/skin-integrity.ts +291 -0
  185. package/src/render-service-host.ts +4 -6
  186. package/src/render.ts +133 -15
  187. package/src/skin-binding.ts +225 -0
  188. package/src/texture-atlas-contract.ts +558 -0
  189. package/src/texture-atlas-pixels.ts +456 -0
  190. package/src/texture-atlas.ts +185 -0
  191. package/src/texture-bake.ts +88 -10
  192. package/src/texture-resolver.ts +55 -0
  193. package/src/tools/materials.ts +15 -11
  194. package/src/tools/registry.ts +5 -15
  195. package/src/tools/review.ts +1 -1
  196. package/src/uv-data.ts +238 -0
  197. package/src/uv-shapes.ts +3 -0
  198. package/src/uv.ts +73 -128
  199. package/src/viewer/app.ts +37 -0
  200. package/src/viewer/live-panel.ts +57 -1
  201. package/src/viewer/material-preview-panel.ts +140 -0
  202. package/src/viewer/material-preview.ts +138 -0
  203. package/src/viewer/materials-panel.ts +34 -5
  204. package/src/viewer/navigation.ts +2 -0
  205. package/src/views/camera.ts +37 -40
  206. package/src/views/face-policy.ts +20 -0
  207. package/src/views/glb.ts +294 -17
  208. package/src/views/pose.ts +71 -13
  209. package/src/views/raster.ts +73 -46
  210. package/src/views/scene-copy.ts +86 -0
  211. package/src/views/scene-raster.ts +2 -1
  212. package/src/views/triangle-coverage.ts +78 -0
  213. package/src/workspace-node.ts +11 -3
  214. package/src/workspace.ts +2 -2
@@ -3,6 +3,7 @@ import * as THREE from 'three';
3
3
  import type { KilnTextureMetadata } from '../textures';
4
4
  import { conformancePromotionAuthorization, KILN_ENGINE_QA_OWNER, type QaRule } from './registry';
5
5
  import type { QaContext, QaFinding } from './types';
6
+ import { inspectAtlasScene } from './atlas';
6
7
 
7
8
  type StandardMaterial = THREE.MeshStandardMaterial & {
8
9
  isMeshStandardMaterial?: boolean;
@@ -276,6 +277,7 @@ export function inspectPortableSceneMaterials(
276
277
  }
277
278
  }
278
279
  });
280
+ findings.push(...inspectAtlasScene(scene).findings);
279
281
  return findings;
280
282
  }
281
283
 
@@ -0,0 +1,291 @@
1
+ import * as THREE from 'three';
2
+ import type { QaFinding } from './types';
3
+
4
+ /** Source serialization invariants, independent of style, body plan or optional QA policy. */
5
+ export interface SkinIntegrityInspectionV1 {
6
+ schemaVersion: 1;
7
+ findings: QaFinding[];
8
+ meshesChecked: number;
9
+ verticesChecked: number;
10
+ truncated: boolean;
11
+ /** Current posed world-space bounds only; not a whole-animation envelope. */
12
+ bounds: { nodePath: string; min: [number, number, number]; max: [number, number, number] }[];
13
+ }
14
+
15
+ const MAX_FINDINGS = 64;
16
+ const WEIGHT_TOLERANCE = 1e-5;
17
+ type Attribute = THREE.BufferAttribute | THREE.InterleavedBufferAttribute;
18
+
19
+ function validAttribute(a: Attribute | undefined, count: number, size: number): a is Attribute {
20
+ if (!a || a.itemSize !== size || a.count !== count || !Number.isSafeInteger(count) || count < 1)
21
+ return false;
22
+ if ('isInterleavedBufferAttribute' in a) {
23
+ return (
24
+ Number.isSafeInteger(a.data.stride) &&
25
+ a.data.stride >= size &&
26
+ Number.isSafeInteger(a.offset) &&
27
+ a.offset >= 0 &&
28
+ a.offset + size <= a.data.stride &&
29
+ (count - 1) * a.data.stride + a.offset + size <= a.array.length
30
+ );
31
+ }
32
+ return count * size <= a.array.length;
33
+ }
34
+
35
+ function finiteAffine(matrix: THREE.Matrix4 | undefined): matrix is THREE.Matrix4 {
36
+ const e = matrix?.elements;
37
+ return (
38
+ !!e &&
39
+ e.length === 16 &&
40
+ [...e].every((v) => Number.isFinite(v) && Number.isFinite(Math.fround(v))) &&
41
+ // Inverting valid TRS matrices can introduce double-precision roundoff.
42
+ // Judge the affine row at the Float32 precision used by the exported data.
43
+ Math.fround(e[3]!) === 0 &&
44
+ Math.fround(e[7]!) === 0 &&
45
+ Math.fround(e[11]!) === 0 &&
46
+ Math.fround(e[15]!) === 1
47
+ );
48
+ }
49
+
50
+ /** Read logical attributes and transforms without updating or repairing the caller's scene. */
51
+ export function inspectSkinIntegrity(root: THREE.Object3D): SkinIntegrityInspectionV1 {
52
+ const result: SkinIntegrityInspectionV1 = {
53
+ schemaVersion: 1,
54
+ findings: [],
55
+ meshesChecked: 0,
56
+ verticesChecked: 0,
57
+ truncated: false,
58
+ bounds: [],
59
+ };
60
+ const paths = new Map<THREE.Object3D, string>();
61
+ const pending: [THREE.Object3D, string, number][] = [[root, '', 0]];
62
+ while (pending.length) {
63
+ const [node, parent, sibling] = pending.pop()!;
64
+ if (paths.has(node)) continue;
65
+ const segment = `${(node.name || node.type).slice(0, 80)}[${sibling}]`;
66
+ const path = (parent ? `${parent}/${segment}` : segment).slice(-800);
67
+ paths.set(node, path);
68
+ for (let i = node.children.length - 1; i >= 0; i--) pending.push([node.children[i]!, path, i]);
69
+ }
70
+ const worlds = new Map<THREE.Object3D, THREE.Matrix4>();
71
+ function world(node: THREE.Object3D): THREE.Matrix4 | undefined {
72
+ const chain: THREE.Object3D[] = [],
73
+ seen = new Set<THREE.Object3D>();
74
+ let cursor: THREE.Object3D | null = node;
75
+ while (cursor && !worlds.has(cursor)) {
76
+ if (seen.has(cursor)) return undefined;
77
+ seen.add(cursor);
78
+ chain.push(cursor);
79
+ cursor = cursor.parent;
80
+ }
81
+ while (chain.length) {
82
+ const n = chain.pop()!;
83
+ const local = n.matrixAutoUpdate
84
+ ? new THREE.Matrix4().compose(n.position, n.quaternion, n.scale)
85
+ : n.matrix.clone();
86
+ const parent = n.parent ? worlds.get(n.parent) : undefined;
87
+ const value =
88
+ n.matrixWorldAutoUpdate === false
89
+ ? n.matrixWorld.clone()
90
+ : parent
91
+ ? new THREE.Matrix4().multiplyMatrices(parent, local)
92
+ : local;
93
+ worlds.set(n, value);
94
+ }
95
+ return worlds.get(node);
96
+ }
97
+ for (const [node, nodePath] of paths) {
98
+ const mesh = node as THREE.SkinnedMesh;
99
+ if (!mesh.isSkinnedMesh) continue;
100
+ if (result.findings.length >= MAX_FINDINGS) {
101
+ result.truncated = true;
102
+ break;
103
+ }
104
+ result.meshesChecked++;
105
+ const start = result.findings.length;
106
+ const add = (
107
+ code: string,
108
+ message: string,
109
+ attribute?: string,
110
+ measurement?: QaFinding['measurement'],
111
+ ) => {
112
+ if (result.findings.length >= MAX_FINDINGS) {
113
+ result.truncated = true;
114
+ return;
115
+ }
116
+ result.findings.push({
117
+ code,
118
+ disposition: 'block',
119
+ dimension: 'exportIntegrity',
120
+ profile: 'skin.source-integrity.v1',
121
+ message,
122
+ affected: { node: mesh.name.slice(0, 160), nodePath, ...(attribute ? { attribute } : {}) },
123
+ ...(measurement ? { measurement } : {}),
124
+ repairText:
125
+ 'Rebuild the named binding at its intended rest pose. Keep bone order, inverse bind matrices and four influence lanes aligned with every final vertex; unwrap or change topology before binding. Do not repair invalid indices by normalizing weights.',
126
+ });
127
+ };
128
+ const skeleton = mesh.skeleton,
129
+ bones = skeleton?.bones;
130
+ if (!bones?.length || !Array.isArray(bones)) {
131
+ add('SKIN_SKELETON', 'Skinned mesh requires a nonempty skeleton in the exported hierarchy.');
132
+ continue;
133
+ }
134
+ const unique = new Set<THREE.Bone>();
135
+ for (let joint = 0; joint < bones.length; joint++) {
136
+ const bone = bones[joint];
137
+ if (!bone?.isObject3D || !paths.has(bone) || unique.has(bone)) {
138
+ add(
139
+ 'SKIN_JOINT_MEMBERSHIP',
140
+ `Joint ${joint} (${JSON.stringify(bone?.name?.slice(0, 160) ?? 'missing')}) must be a distinct node inside the exported hierarchy.`,
141
+ `joints[${joint}]`,
142
+ );
143
+ }
144
+ if (bone) unique.add(bone);
145
+ if (!finiteAffine(skeleton.boneInverses?.[joint])) {
146
+ add(
147
+ 'SKIN_INVERSE_BIND',
148
+ `Joint ${joint} (${JSON.stringify(bone?.name?.slice(0, 160) ?? 'missing')}) requires a finite affine inverse bind matrix.`,
149
+ `boneInverses[${joint}]`,
150
+ );
151
+ }
152
+ if (result.truncated) break;
153
+ }
154
+ if (
155
+ !finiteAffine(mesh.bindMatrix) ||
156
+ !Number.isFinite(mesh.bindMatrix.determinant()) ||
157
+ mesh.bindMatrix.determinant() === 0 ||
158
+ ![THREE.AttachedBindMode, THREE.DetachedBindMode].includes(mesh.bindMode)
159
+ ) {
160
+ add(
161
+ 'SKIN_BIND_MATRIX',
162
+ 'Mesh requires a finite invertible affine bind matrix and an attached or detached bind mode.',
163
+ 'bindMatrix',
164
+ );
165
+ }
166
+ const position = mesh.geometry?.getAttribute('position'),
167
+ count = position?.count ?? 0;
168
+ const indices = mesh.geometry?.getAttribute('skinIndex'),
169
+ weights = mesh.geometry?.getAttribute('skinWeight');
170
+ if (
171
+ !validAttribute(position, count, 3) ||
172
+ !validAttribute(indices, count, 4) ||
173
+ indices.normalized ||
174
+ !validAttribute(weights, count, 4)
175
+ ) {
176
+ add(
177
+ 'SKIN_ATTRIBUTES',
178
+ 'Skin requires xyz positions and four non-normalized joint indices and four weights per final vertex, backed by complete buffers.',
179
+ 'skinIndex/skinWeight',
180
+ );
181
+ continue;
182
+ }
183
+ const invalid = new Map<
184
+ string,
185
+ { count: number; first: number; last: number; attribute: string }
186
+ >();
187
+ const bad = (code: string, vertex: number, attribute: string) => {
188
+ const row = invalid.get(code);
189
+ if (row) {
190
+ if (row.last !== vertex) row.count++;
191
+ row.last = vertex;
192
+ } else invalid.set(code, { count: 1, first: vertex, last: vertex, attribute });
193
+ };
194
+ for (let vertex = 0; vertex < count; vertex++) {
195
+ let sum = 0;
196
+ for (let lane = 0; lane < 4; lane++) {
197
+ const index = indices.getComponent(vertex, lane),
198
+ weight = weights.getComponent(vertex, lane);
199
+ if (!Number.isInteger(index) || index < 0 || index >= bones.length)
200
+ bad('SKIN_JOINT_INDEX', vertex, 'skinIndex');
201
+ if (!Number.isFinite(weight) || weight < 0 || weight > 1)
202
+ bad('SKIN_WEIGHT_VALUE', vertex, 'skinWeight');
203
+ sum += weight;
204
+ }
205
+ if (!Number.isFinite(sum) || Math.abs(sum - 1) > WEIGHT_TOLERANCE)
206
+ bad('SKIN_WEIGHT_SUM', vertex, 'skinWeight');
207
+ }
208
+ result.verticesChecked += count;
209
+ for (const [code, row] of invalid) {
210
+ const why =
211
+ code === 'SKIN_JOINT_INDEX'
212
+ ? 'indices must address the skeleton, including zero-weight lanes'
213
+ : code === 'SKIN_WEIGHT_SUM'
214
+ ? `weights must sum to one within ${WEIGHT_TOLERANCE}`
215
+ : 'weights must be finite values in [0,1]';
216
+ add(
217
+ code,
218
+ `Invalid ${row.attribute} at vertex range ${row.first}..${row.last} (${row.count} affected vertices): ${why}.`,
219
+ row.attribute,
220
+ {
221
+ name: 'invalidSkinVertices',
222
+ actual: row.count,
223
+ expected: 0,
224
+ breakdown: { firstVertex: row.first, lastVertex: row.last },
225
+ },
226
+ );
227
+ }
228
+ if (result.findings.length !== start) continue;
229
+ const meshWorld = world(mesh);
230
+ if (!finiteAffine(meshWorld)) {
231
+ add('SKIN_POSE_TRANSFORM', 'Current mesh world transform must be finite and affine.');
232
+ continue;
233
+ }
234
+ const inverse =
235
+ mesh.bindMode === THREE.AttachedBindMode
236
+ ? meshWorld.clone().invert()
237
+ : mesh.bindMatrix.clone().invert();
238
+ const matrices: THREE.Matrix4[] = [];
239
+ for (let joint = 0; joint < bones.length; joint++) {
240
+ const boneWorld = world(bones[joint]!);
241
+ if (!finiteAffine(boneWorld)) {
242
+ add(
243
+ 'SKIN_POSE_TRANSFORM',
244
+ `Current transform for joint ${joint} must be finite and affine.`,
245
+ `joints[${joint}]`,
246
+ );
247
+ break;
248
+ }
249
+ matrices.push(
250
+ new THREE.Matrix4()
251
+ .multiplyMatrices(boneWorld, skeleton.boneInverses[joint]!)
252
+ .multiply(mesh.bindMatrix),
253
+ );
254
+ }
255
+ if (result.findings.length !== start) continue;
256
+ const base = new THREE.Vector3(),
257
+ posed = new THREE.Vector3(),
258
+ temp = new THREE.Vector3(),
259
+ box = new THREE.Box3();
260
+ for (let vertex = 0; vertex < count; vertex++) {
261
+ // Mesh's implementation evaluates morphs but does not invoke the unsafe skin path.
262
+ THREE.Mesh.prototype.getVertexPosition.call(mesh, vertex, base);
263
+ posed.set(0, 0, 0);
264
+ for (let lane = 0; lane < 4; lane++) {
265
+ const weight = weights.getComponent(vertex, lane);
266
+ if (weight !== 0)
267
+ posed.addScaledVector(
268
+ temp.copy(base).applyMatrix4(matrices[indices.getComponent(vertex, lane)]!),
269
+ weight,
270
+ );
271
+ }
272
+ posed.applyMatrix4(inverse).applyMatrix4(meshWorld);
273
+ if (
274
+ !Number.isFinite(Math.fround(posed.x)) ||
275
+ !Number.isFinite(Math.fround(posed.y)) ||
276
+ !Number.isFinite(Math.fround(posed.z))
277
+ ) {
278
+ add(
279
+ 'SKIN_POSED_VERTEX',
280
+ `Current deformed vertex ${vertex} is not finite and Float32-representable. Check positions, morphs and weighted joint transforms.`,
281
+ `position[${vertex}]`,
282
+ );
283
+ break;
284
+ }
285
+ box.expandByPoint(posed);
286
+ }
287
+ if (result.findings.length === start)
288
+ result.bounds.push({ nodePath, min: box.min.toArray(), max: box.max.toArray() });
289
+ }
290
+ return result;
291
+ }
@@ -248,7 +248,6 @@ export async function startLocalRenderService(
248
248
  stderr = `${stderr}${chunk.toString()}`.slice(-2_000);
249
249
  });
250
250
  const deadline = Date.now() + budget;
251
- let exitedAt: number | undefined;
252
251
  try {
253
252
  while (Date.now() < deadline) {
254
253
  const fresh = await inspectLocalRenderService(url, dir, 1_000);
@@ -258,11 +257,10 @@ export async function startLocalRenderService(
258
257
  const exited = windowsLaunch
259
258
  ? windowsLaunch.hasExited()
260
259
  : running.exitCode !== null || running.signalCode !== null;
261
- if (exited) exitedAt ??= Date.now();
262
- // Another client may have won the socket while this child lost its bind. Give
263
- // that listener a bounded health warmup, and join only after full verification.
264
- // A failed spawn has no such race; absent/unknown never count as success.
265
- if (spawnError || (exitedAt !== undefined && Date.now() - exitedAt >= 1500))
260
+ // A competing child can win the socket while this one exits after losing
261
+ // its bind. An unknown listener gets the same startup deadline as our child;
262
+ // only verified health permits joining. An exited child with no listener fails.
263
+ if (spawnError || (exited && fresh.kind === 'absent'))
266
264
  throw new Error(
267
265
  `render service exited during startup${spawnError ? `: ${spawnError.message}` : ''}${stderr.trim() ? `: ${stderr.trim()}` : windowsLaunch ? '; launch the renderer manually to inspect native-driver errors' : ''}`,
268
266
  );