@woosh/meep-engine 3.14.3 → 3.14.5

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 (137) hide show
  1. package/package.json +1 -1
  2. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
  3. package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
  4. package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
  5. package/src/engine/Engine.d.ts.map +1 -1
  6. package/src/engine/Engine.js +733 -726
  7. package/src/engine/network/NetworkSession.d.ts +24 -4
  8. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  9. package/src/engine/network/NetworkSession.js +28 -4
  10. package/src/engine/network/orchestrator/NetworkPeer.d.ts +96 -7
  11. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  12. package/src/engine/network/orchestrator/NetworkPeer.js +392 -68
  13. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
  14. package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
  15. package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
  16. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +105 -13
  17. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  18. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +306 -59
  19. package/src/engine/network/replication/Replicator.d.ts +43 -7
  20. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  21. package/src/engine/network/replication/Replicator.js +309 -111
  22. package/src/engine/network/sim/ActionLog.d.ts +23 -3
  23. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  24. package/src/engine/network/sim/ActionLog.js +26 -3
  25. package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
  26. package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
  27. package/src/engine/network/sim/ActionRecordCursor.js +120 -0
  28. package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
  29. package/src/engine/network/sim/RewindEngine.js +13 -29
  30. package/src/engine/network/sim/SimAction.d.ts +15 -0
  31. package/src/engine/network/sim/SimAction.d.ts.map +1 -1
  32. package/src/engine/network/sim/SimAction.js +15 -0
  33. package/src/engine/network/sim/SimActionExecutor.d.ts +51 -2
  34. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  35. package/src/engine/network/sim/SimActionExecutor.js +74 -7
  36. package/src/engine/network/transport/Channel.d.ts +1 -1
  37. package/src/engine/network/transport/Channel.js +1 -1
  38. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  39. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  40. package/src/engine/simulation/Ticker.js +4 -1
  41. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  42. package/src/engine/ui/GUIEngine.js +6 -0
  43. package/src/shade/playground/particle_system/README.md +98 -0
  44. package/src/shade/playground/particle_system/index.html +124 -0
  45. package/src/shade/playground/particle_system/main.d.ts +2 -0
  46. package/src/shade/playground/particle_system/main.d.ts.map +1 -0
  47. package/src/shade/playground/particle_system/main.js +180 -0
  48. package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
  49. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
  50. package/src/shade/playground/particle_system/particle_scene.js +553 -0
  51. package/src/shade/playground/skinned_mesh_soup/README.md +143 -0
  52. package/src/shade/playground/skinned_mesh_soup/index.html +202 -0
  53. package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
  54. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
  55. package/src/shade/playground/skinned_mesh_soup/main.js +567 -0
  56. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +29 -0
  57. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
  58. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +428 -0
  59. package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
  60. package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
  61. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
  62. package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
  63. package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
  64. package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
  65. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
  66. package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
  67. package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
  68. package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
  69. package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
  70. package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
  71. package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
  72. package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
  73. package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
  74. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
  75. package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
  76. package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
  77. package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
  78. package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
  79. package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
  80. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
  81. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
  82. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
  83. package/src/shade/renderer/particles/DESIGN.md +5 -5
  84. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
  85. package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
  86. package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
  87. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
  88. package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
  89. package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
  90. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
  91. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
  92. package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
  93. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
  94. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  95. package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
  96. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
  97. package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
  98. package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
  99. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
  100. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
  101. package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
  102. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
  103. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
  104. package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
  105. package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
  106. package/src/shade/renderer/particles/particle_prototype.js +181 -180
  107. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
  108. package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
  109. package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
  110. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
  111. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
  112. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
  113. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  114. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
  115. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  116. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
  117. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  118. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
  119. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  120. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
  121. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  122. package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
  123. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  124. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
  125. package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
  126. package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
  127. package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
  128. package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
  129. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
  130. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
  131. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
  132. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
  133. package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
  134. package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
  135. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
  136. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
  137. package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
@@ -0,0 +1,428 @@
1
+ /**
2
+ * Read a clone's deformed vertex region back and hold it against an answer the CPU knows.
3
+ *
4
+ * The page can be looked at, and soup is not subtle. What looking at it cannot say is *which*
5
+ * vertices went wrong and in what pattern, and that is the whole difference between "skinning is
6
+ * broken" and a bug you can name: every vertex past the 64th of its meshlet is the workgroup stride
7
+ * loop, every vertex of some meshlets and none of others is the per-meshlet addressing, a scatter
8
+ * with no pattern is the joint palette.
9
+ *
10
+ * This runs in the **bind pose** — `?skin=bind`, no clip registered — because that is the one pose
11
+ * whose answer needs no GPU. At bind, `joint.global × inverse_bind` is the identity for every joint
12
+ * (verified on the CPU for Michelle: every one of her 19 106 vertices lands back on its rest
13
+ * position to the bit), so the blend collapses whatever the weights are and the shader's output is
14
+ * just its input under the mesh-local fold:
15
+ *
16
+ * ```
17
+ * clone_position = inverse(mesh.transform_global) × source_position
18
+ * ```
19
+ *
20
+ * — the leftmost factor of the glTF skinning formula, which `chunk_skin_blend_mesh_local` folds
21
+ * into each joint matrix so the rasterizer can re-apply `mesh.global` afterwards. Nothing about
22
+ * that expression involves the pose, so a disagreement here is the skinning *machinery* — the
23
+ * addressing, the layout, the matrix range — and not the animation.
24
+ *
25
+ * @author Alex Goldring
26
+ * @copyright Company Named Limited (c) 2026
27
+ */
28
+
29
+ import { assert } from "../../../core/assert.js";
30
+ import {
31
+ MESHLET_METADATA_STRUCT
32
+ } from "../../renderer/geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
33
+ import {
34
+ GEOMETRY_METADATA_STRUCT
35
+ } from "../../renderer/geometry/GEOMETRY_METADATA_STRUCT.js";
36
+ import {
37
+ SKINNING_WORKGROUP_SIZE
38
+ } from "../../renderer/animation/skinning/shader_meshlet_apply_skinning.js";
39
+
40
+ /**
41
+ * Copy a slice of a GPU buffer back to the CPU.
42
+ *
43
+ * @param {GPUDevice} device
44
+ * @param {GPUBuffer} buffer
45
+ * @param {number} offset in bytes, a multiple of 4
46
+ * @param {number} size in bytes, a multiple of 4
47
+ * @returns {Promise<ArrayBuffer>}
48
+ */
49
+ async function read_back(device, buffer, offset, size) {
50
+ const staging = device.createBuffer({
51
+ label: 'soup/readback',
52
+ size,
53
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
54
+ });
55
+
56
+ const encoder = device.createCommandEncoder({ label: 'soup/readback' });
57
+
58
+ encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
59
+
60
+ device.queue.submit([encoder.finish()]);
61
+
62
+ await staging.mapAsync(GPUMapMode.READ);
63
+
64
+ const copy = staging.getMappedRange().slice(0);
65
+
66
+ staging.unmap();
67
+ staging.destroy();
68
+
69
+ return copy;
70
+ }
71
+
72
+ /**
73
+ * Transform a point by a column-major 4×4.
74
+ *
75
+ * @param {ArrayLike<number>} m
76
+ * @param {number} x
77
+ * @param {number} y
78
+ * @param {number} z
79
+ * @returns {number[]}
80
+ */
81
+ function transform_point(m, x, y, z) {
82
+ return [
83
+ m[0] * x + m[4] * y + m[8] * z + m[12],
84
+ m[1] * x + m[5] * y + m[9] * z + m[13],
85
+ m[2] * x + m[6] * y + m[10] * z + m[14],
86
+ ];
87
+ }
88
+
89
+ /**
90
+ * Invert a column-major 4×4 that is an affine transform — which every node transform in this engine
91
+ * is. General enough for a `T · R · S` with non-uniform scale, and not for a projection.
92
+ *
93
+ * @param {ArrayLike<number>} m
94
+ * @returns {Float64Array}
95
+ */
96
+ function affine_inverse(m) {
97
+ const a = m[0], b = m[4], c = m[8];
98
+ const d = m[1], e = m[5], f = m[9];
99
+ const g = m[2], h = m[6], i = m[10];
100
+
101
+ const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
102
+
103
+ assert.notEqual(det, 0, 'transform is singular');
104
+
105
+ const s = 1 / det;
106
+
107
+ const r = new Float64Array(16);
108
+
109
+ r[0] = (e * i - f * h) * s;
110
+ r[1] = (f * g - d * i) * s;
111
+ r[2] = (d * h - e * g) * s;
112
+
113
+ r[4] = (c * h - b * i) * s;
114
+ r[5] = (a * i - c * g) * s;
115
+ r[6] = (b * g - a * h) * s;
116
+
117
+ r[8] = (b * f - c * e) * s;
118
+ r[9] = (c * d - a * f) * s;
119
+ r[10] = (a * e - b * d) * s;
120
+
121
+ const tx = m[12], ty = m[13], tz = m[14];
122
+
123
+ r[12] = -(r[0] * tx + r[4] * ty + r[8] * tz);
124
+ r[13] = -(r[1] * tx + r[5] * ty + r[9] * tz);
125
+ r[14] = -(r[2] * tx + r[6] * ty + r[10] * tz);
126
+
127
+ r[15] = 1;
128
+
129
+ return r;
130
+ }
131
+
132
+ /**
133
+ * Contiguous runs of a sorted list of integers, as `[first, last]` pairs.
134
+ *
135
+ * @param {number[]} values
136
+ * @returns {number[][]}
137
+ */
138
+ function runs_of(values) {
139
+ const runs = [];
140
+
141
+ for (const value of values) {
142
+ const last = runs[runs.length - 1];
143
+
144
+ if (last !== undefined && last[1] === value - 1) {
145
+ last[1] = value;
146
+ } else {
147
+ runs.push([value, value]);
148
+ }
149
+ }
150
+
151
+ return runs;
152
+ }
153
+
154
+ /**
155
+ * The clone's deformed vertices, against `inverse(mesh.global) × rest`.
156
+ *
157
+ * Reads the GPU's own answer to "where does this geometry id's data live" rather than reconstructing
158
+ * it from the host records: the geometry metadata row, then the clone's meshlet metadata rows, then
159
+ * the data those rows address. That is the same chain the rasterizer and the intersector walk, so a
160
+ * clone whose *addressing* is wrong is caught here rather than being read around.
161
+ *
162
+ * @param {object} options
163
+ * @param {Renderer} options.renderer
164
+ * @param {Scene} options.scene
165
+ * @param {SkinnedMesh} options.mesh
166
+ * @param {number} [options.tolerance] in mesh-local units. Since `register_skin` puts the mesh on
167
+ * the skin's bind transform, mesh-local *is* world scale — a metre is a metre here, and at
168
+ * bind the clone should hold the source's own positions. `1e-4` is f32 headroom over a
169
+ * chain — a matrix product, a quaternion conversion and a screw motion, all at single
170
+ * precision — against a smallest real violation of `1.6`, so there are four orders between
171
+ * the two. It was `5e-3` while `dual_quat_from_m4` amplified a near-identity matrix's error
172
+ * by its square root; `SKINNING_FRAME_PLAN.md` §9 took that out, and the same measurement on
173
+ * the CPU now reads zero at bind.
174
+ * @returns {Promise<object>}
175
+ */
176
+ export async function verify_clone_vertices({ renderer, scene, mesh, tolerance = 1e-4 }) {
177
+ assert.defined(mesh, 'mesh');
178
+ assert.equal(mesh.isSkinnedMesh, true, 'mesh.isSkinnedMesh !== true');
179
+
180
+ const graphics = renderer.graphics;
181
+ const device = graphics.device;
182
+ const geometries = graphics.geometries;
183
+
184
+ const scene_context = renderer.scenes.obtain(scene);
185
+
186
+ const geometry_index = scene_context.skinning.obtain_geometry_index(mesh);
187
+
188
+ // ---- where the clone's meshlets are, as the GPU has it -------------------------------------
189
+
190
+ const geometry_row = new DataView(await read_back(
191
+ device,
192
+ geometries.buffer_metadata,
193
+ geometry_index * GEOMETRY_METADATA_STRUCT.size,
194
+ GEOMETRY_METADATA_STRUCT.size
195
+ ));
196
+
197
+ const meshlets_address = geometry_row.getUint32(
198
+ GEOMETRY_METADATA_STRUCT.get('meshlets_address').offset, true
199
+ );
200
+ const meshlets_count = geometry_row.getUint32(
201
+ GEOMETRY_METADATA_STRUCT.get('meshlets_count').offset, true
202
+ );
203
+
204
+ const source_batch = mesh.geometry.meshlets;
205
+
206
+ if (meshlets_count !== source_batch.count) {
207
+ return {
208
+ geometry_index,
209
+ verdict: `FAIL — the clone's geometry row claims ${meshlets_count} meshlets, `
210
+ + `its source has ${source_batch.count}`,
211
+ };
212
+ }
213
+
214
+ const meshlet_stride = MESHLET_METADATA_STRUCT.aligned_size;
215
+
216
+ const clone_metadata = new DataView(await read_back(
217
+ device,
218
+ geometries.meshlets.buffer_metadata,
219
+ meshlets_address * meshlet_stride,
220
+ meshlets_count * meshlet_stride
221
+ ));
222
+
223
+ const ADDRESS_OFFSET = MESHLET_METADATA_STRUCT.get('address').offset;
224
+ const VERTEX_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('vertex_count').offset;
225
+ const PRIMITIVE_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('primitive_count').offset;
226
+
227
+ // ---- and where the source's are, to say the clone is somewhere else --------------------------
228
+
229
+ /*
230
+ A clone is a fresh vertex region plus a patch pass that adds `clone.data.offset -
231
+ source.data.offset` to every one of its meshlet records. If that patch does not land, the
232
+ clone's records still address the *source's* data: the skinning pass then deforms the shared
233
+ rest-pose vertices in place, every instance of the model deforms on top of every other, and the
234
+ source no rasterizer ever wanted deformed goes with them. The addresses differing by one
235
+ constant is the whole contract, and it is checkable before a single vertex is compared.
236
+ */
237
+ const source_record = geometries.get_metadata(mesh.geometry);
238
+
239
+ if (source_record === undefined) {
240
+ return {
241
+ geometry_index,
242
+ verdict: `FAIL — the mesh's source geometry is not registered, so there is nothing for `
243
+ + `its clone to be a clone of. The scene has not been built with this mesh in it.`,
244
+ };
245
+ }
246
+
247
+ const source_metadata = new DataView(await read_back(
248
+ device,
249
+ geometries.meshlets.buffer_metadata,
250
+ source_record.meshlets.metadata.offset * meshlet_stride,
251
+ meshlets_count * meshlet_stride
252
+ ));
253
+
254
+ // ---- the clone's vertex data, in one read ---------------------------------------------------
255
+
256
+ /*
257
+ Position is the first attribute of the section and is never collapsed to a shared slot (see
258
+ ENCODED_ORDERED_ATTRIBUTES and MESHLET_ATTRIBUTE_COMPRESSION_FLAGS), so a meshlet's positions
259
+ start where its attribute section does and the furthest word this needs is the last position
260
+ word of the furthest meshlet. That is a span, not a set of holes, so it comes back in one copy
261
+ rather than in `meshlets_count` of them — two hundred round trips through `mapAsync` is a
262
+ diagnostic nobody runs twice.
263
+ */
264
+ const position_word_of = new Uint32Array(meshlets_count);
265
+
266
+ let data_low = Infinity;
267
+ let data_high = 0;
268
+
269
+ /** every distinct `clone address − source address`; one entry is the contract */
270
+ const address_deltas = new Set();
271
+
272
+ for (let m = 0; m < meshlets_count; m++) {
273
+ const row = m * meshlet_stride;
274
+
275
+ const address = clone_metadata.getUint32(row + ADDRESS_OFFSET, true);
276
+ const primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
277
+ const vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
278
+
279
+ address_deltas.add(address - source_metadata.getUint32(row + ADDRESS_OFFSET, true));
280
+
281
+ const position_word = address + ((primitive_count * 3 + 3) >> 2);
282
+
283
+ position_word_of[m] = position_word;
284
+
285
+ data_low = Math.min(data_low, position_word);
286
+ data_high = Math.max(data_high, position_word + vertex_count * 3);
287
+ }
288
+
289
+ if (address_deltas.size !== 1 || address_deltas.has(0)) {
290
+ return {
291
+ geometry_index,
292
+ address_deltas: [...address_deltas].slice(0, 8),
293
+ verdict: address_deltas.has(0) && address_deltas.size === 1
294
+ ? `FAIL — every one of the clone's ${meshlets_count} meshlet records addresses the `
295
+ + `source's vertex data. The address patch did not run, so the skinning pass is `
296
+ + `deforming the shared rest-pose region in place.`
297
+ : `FAIL — the clone's meshlet records sit at ${address_deltas.size} different `
298
+ + `offsets from the source's; a clone is one contiguous region and its patch is `
299
+ + `one delta.`,
300
+ };
301
+ }
302
+
303
+ const data = new Float32Array(await read_back(
304
+ device,
305
+ geometries.meshlets.buffer_data,
306
+ data_low * 4,
307
+ (data_high - data_low) * 4
308
+ ));
309
+
310
+ // ---- the answer the CPU knows ---------------------------------------------------------------
311
+
312
+ const to_mesh_local = affine_inverse(mesh.transform_global.matrix);
313
+
314
+ // ---- and the comparison ---------------------------------------------------------------------
315
+
316
+ let vertices = 0;
317
+ let wrong = 0;
318
+ let worst = 0;
319
+ let worst_where = null;
320
+
321
+ /** meshlets with at least one wrong vertex */
322
+ const wrong_meshlets = [];
323
+
324
+ /** per-vertex-slot tally: how many meshlets got slot `v` wrong */
325
+ const wrong_by_slot = new Map();
326
+
327
+ const examples = [];
328
+
329
+ for (let m = 0; m < meshlets_count; m++) {
330
+ const source_header = source_batch.read_header(m);
331
+ const source = source_batch.read_meshlet(m);
332
+
333
+ const row = m * meshlet_stride;
334
+
335
+ const clone_vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
336
+ const clone_primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
337
+
338
+ if (clone_vertex_count !== source_header.vertex_count
339
+ || clone_primitive_count !== source_header.primitive_count) {
340
+ return {
341
+ geometry_index,
342
+ verdict: `FAIL — clone meshlet ${m} says `
343
+ + `${clone_vertex_count} vertices / ${clone_primitive_count} triangles, `
344
+ + `its source says ${source_header.vertex_count} / ${source_header.primitive_count}. `
345
+ + `The metadata copy or its address patch is wrong, before any skinning ran.`,
346
+ };
347
+ }
348
+
349
+ const positions = data.subarray(
350
+ position_word_of[m] - data_low,
351
+ position_word_of[m] - data_low + clone_vertex_count * 3
352
+ );
353
+
354
+ let meshlet_wrong = 0;
355
+
356
+ for (let v = 0; v < clone_vertex_count; v++) {
357
+ vertices++;
358
+
359
+ const expected = transform_point(
360
+ to_mesh_local,
361
+ source.attribute_position[v * 3],
362
+ source.attribute_position[v * 3 + 1],
363
+ source.attribute_position[v * 3 + 2],
364
+ );
365
+
366
+ const error = Math.max(
367
+ Math.abs(positions[v * 3] - expected[0]),
368
+ Math.abs(positions[v * 3 + 1] - expected[1]),
369
+ Math.abs(positions[v * 3 + 2] - expected[2]),
370
+ );
371
+
372
+ if (error > worst) {
373
+ worst = error;
374
+ worst_where = { meshlet: m, vertex: v };
375
+ }
376
+
377
+ if (error > tolerance) {
378
+ wrong++;
379
+ meshlet_wrong++;
380
+
381
+ wrong_by_slot.set(v, (wrong_by_slot.get(v) ?? 0) + 1);
382
+
383
+ if (examples.length < 8) {
384
+ examples.push({
385
+ meshlet: m,
386
+ vertex: v,
387
+ got: [positions[v * 3], positions[v * 3 + 1], positions[v * 3 + 2]],
388
+ expected,
389
+ });
390
+ }
391
+ }
392
+ }
393
+
394
+ if (meshlet_wrong > 0) {
395
+ wrong_meshlets.push(m);
396
+ }
397
+ }
398
+
399
+ // The two shapes worth telling apart, named rather than left to be read out of the runs: a
400
+ // stride-loop bug wrongs every slot at or past the workgroup size and nothing below it, and an
401
+ // addressing bug wrongs whole meshlets.
402
+ const slots = [...wrong_by_slot.keys()].sort((a, b) => a - b);
403
+
404
+ const lowest_wrong_slot = slots.length > 0 ? slots[0] : null;
405
+
406
+ return {
407
+ geometry_index,
408
+ meshlets: meshlets_count,
409
+ address_delta: [...address_deltas][0],
410
+ vertices,
411
+ wrong,
412
+ worst,
413
+ worst_where,
414
+ wrong_meshlets: wrong_meshlets.length,
415
+ wrong_meshlet_runs: runs_of(wrong_meshlets).slice(0, 20),
416
+ lowest_wrong_slot,
417
+ highest_correct_slot: slots.length > 0 ? lowest_wrong_slot - 1 : null,
418
+ wrong_slot_runs: runs_of(slots).slice(0, 20),
419
+ examples,
420
+ verdict: wrong === 0
421
+ ? `pass — all ${vertices} of the clone's vertices are the bind pose, worst error ${worst}`
422
+ : `FAIL — ${wrong} of ${vertices} vertices in ${wrong_meshlets.length} of `
423
+ + `${meshlets_count} meshlets are not the bind pose (worst ${worst}); `
424
+ + `lowest wrong vertex slot ${lowest_wrong_slot}. `
425
+ + `A lowest slot of ${SKINNING_WORKGROUP_SIZE} is the stride loop; whole meshlets `
426
+ + `wrong is addressing; a uniform factor is the frame.`,
427
+ };
428
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"AA4HA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IA0NI;;;;OAIG;IACH,sCAHW,MAAM,kCAgBhB;IAED;;OAEG;IACH,4BAEC;IAED;;;;;;;;;;;;OAYG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,qCAEC;IAED;;;;;;OAMG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,mCAEC;IAED;;;;;;;;;;;;OAYG;IACH,4CAFW,MAAM,QA6BhB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAK3D;IAED;;;;;;;;;;;;OAYG;IACH,gCAHW,MAAM,GACJ,MAAM,CAOlB;IAED;;;;;;;;;;;OAWG;IACH,kCAFa,MAAM,CA+BlB;IAED;;;;;;;;;;;;;;OAcG;IACH,oBAJW;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAEhD,MAAM,CA2BlB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH;eAPc,MAAM;qBACA,MAAM;mBACR,MAAM;kBACP,MAAM;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAEhE,MAAM,CAkElB;IAgCD;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;OAQG;IACH,2BAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;OAcG;IACH,6BAHW,MAAM,UACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,mBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;OAOG;IACH,qBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;;;;;OAWG;IACH,eAFW,MAAM,QAShB;IAED;;;;;;;;;OASG;IACH,cAFW,MAAM,QAShB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH;gBAJe,MAAM;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAAA;SAAE,CAAC;QAEhE,MAAM,CAoElB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,2BAFa,MAAM,CA4ElB;IA8ED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,yCAFa,MAAM,CAyDlB;IAwDD;;;;;;;;;;;;;;;OAeG;IACH,yBAFW,MAAM,QAkChB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,yBAFW,MAAM,QA2ChB;IAqHD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,8CAIC;IAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,iEA2CC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kDAFW,MAAM,QAgChB;IA8FD,gBAYC;;CACJ;4BAjwD2B,gCAAgC"}
1
+ {"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"AAiIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IA6OI;;;;OAIG;IACH,sCAHW,MAAM,kCAgBhB;IAED;;OAEG;IACH,4BAEC;IAED;;;;;;;;;;;;OAYG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,qCAEC;IAED;;;;;;OAMG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,mCAEC;IAED;;;;;;;;;;;;OAYG;IACH,4CAFW,MAAM,QA6BhB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAK3D;IAED;;;;;;;;;;;;OAYG;IACH,gCAHW,MAAM,GACJ,MAAM,CAOlB;IAED;;;;;;;;;;;OAWG;IACH,kCAFa,MAAM,CA+BlB;IAED;;;;;;;;;;;;;;OAcG;IACH,oBAJW;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAEhD,MAAM,CA2BlB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH;eAPc,MAAM;qBACA,MAAM;mBACR,MAAM;kBACP,MAAM;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAEhE,MAAM,CAkElB;IAgCD;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;OAQG;IACH,2BAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;OAcG;IACH,6BAHW,MAAM,UACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,mBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;OAOG;IACH,qBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;;;;;OAWG;IACH,eAFW,MAAM,QAShB;IAED;;;;;;;;;OASG;IACH,cAFW,MAAM,QAShB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH;gBAJe,MAAM;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAAA;SAAE,CAAC;QAEhE,MAAM,CAoElB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,2BAFa,MAAM,CA0FlB;IAyLD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,yCAFa,MAAM,CAsElB;IAwDD;;;;;;;;;;;;;;;OAeG;IACH,yBAFW,MAAM,QAoChB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,yBAFW,MAAM,QA6ChB;IAqHD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,8CAIC;IAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,iEA2CC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kDAFW,MAAM,QAgChB;IA8FD,gBAYC;;CACJ;4BAn6D2B,gCAAgC"}