@woosh/meep-engine 3.14.3 → 3.14.4

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 (75) hide show
  1. package/package.json +1 -1
  2. package/src/engine/Engine.d.ts.map +1 -1
  3. package/src/engine/Engine.js +733 -726
  4. package/src/engine/network/NetworkSession.d.ts +24 -4
  5. package/src/engine/network/NetworkSession.d.ts.map +1 -1
  6. package/src/engine/network/NetworkSession.js +28 -4
  7. package/src/engine/network/orchestrator/NetworkPeer.d.ts +47 -6
  8. package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
  9. package/src/engine/network/orchestrator/NetworkPeer.js +184 -56
  10. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +97 -11
  11. package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
  12. package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +304 -22
  13. package/src/engine/network/replication/Replicator.d.ts +14 -1
  14. package/src/engine/network/replication/Replicator.d.ts.map +1 -1
  15. package/src/engine/network/replication/Replicator.js +44 -3
  16. package/src/engine/network/sim/ActionLog.d.ts +21 -3
  17. package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
  18. package/src/engine/network/sim/ActionLog.js +24 -3
  19. package/src/engine/network/sim/SimAction.d.ts +15 -0
  20. package/src/engine/network/sim/SimAction.d.ts.map +1 -1
  21. package/src/engine/network/sim/SimAction.js +15 -0
  22. package/src/engine/network/sim/SimActionExecutor.d.ts +51 -1
  23. package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
  24. package/src/engine/network/sim/SimActionExecutor.js +73 -5
  25. package/src/engine/network/transport/Channel.d.ts +1 -1
  26. package/src/engine/network/transport/Channel.js +1 -1
  27. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  28. package/src/engine/simulation/Ticker.js +4 -1
  29. package/src/engine/ui/GUIEngine.d.ts.map +1 -1
  30. package/src/engine/ui/GUIEngine.js +6 -0
  31. package/src/shade/playground/particle_system/README.md +98 -0
  32. package/src/shade/playground/particle_system/index.html +124 -0
  33. package/src/shade/playground/particle_system/main.d.ts +2 -0
  34. package/src/shade/playground/particle_system/main.d.ts.map +1 -0
  35. package/src/shade/playground/particle_system/main.js +180 -0
  36. package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
  37. package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
  38. package/src/shade/playground/particle_system/particle_scene.js +552 -0
  39. package/src/shade/playground/skinned_mesh_soup/README.md +122 -0
  40. package/src/shade/playground/skinned_mesh_soup/index.html +199 -0
  41. package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
  42. package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
  43. package/src/shade/playground/skinned_mesh_soup/main.js +564 -0
  44. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +23 -0
  45. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
  46. package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +409 -0
  47. package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
  48. package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
  49. package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
  50. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +1 -1
  51. package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
  52. package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
  53. package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
  54. package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
  55. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
  56. package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
  57. package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
  58. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
  59. package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
  60. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
  61. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
  62. package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
  63. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
  64. package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
  65. package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
  66. package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
  67. package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
  68. package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
  69. package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
  70. package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
  71. package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
  72. package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
  73. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
  74. package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
  75. package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
@@ -0,0 +1,409 @@
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
+
37
+ /**
38
+ * Copy a slice of a GPU buffer back to the CPU.
39
+ *
40
+ * @param {GPUDevice} device
41
+ * @param {GPUBuffer} buffer
42
+ * @param {number} offset in bytes, a multiple of 4
43
+ * @param {number} size in bytes, a multiple of 4
44
+ * @returns {Promise<ArrayBuffer>}
45
+ */
46
+ async function read_back(device, buffer, offset, size) {
47
+ const staging = device.createBuffer({
48
+ label: 'soup/readback',
49
+ size,
50
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
51
+ });
52
+
53
+ const encoder = device.createCommandEncoder({ label: 'soup/readback' });
54
+
55
+ encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
56
+
57
+ device.queue.submit([encoder.finish()]);
58
+
59
+ await staging.mapAsync(GPUMapMode.READ);
60
+
61
+ const copy = staging.getMappedRange().slice(0);
62
+
63
+ staging.unmap();
64
+ staging.destroy();
65
+
66
+ return copy;
67
+ }
68
+
69
+ /**
70
+ * Transform a point by a column-major 4×4.
71
+ *
72
+ * @param {ArrayLike<number>} m
73
+ * @param {number} x
74
+ * @param {number} y
75
+ * @param {number} z
76
+ * @returns {number[]}
77
+ */
78
+ function transform_point(m, x, y, z) {
79
+ return [
80
+ m[0] * x + m[4] * y + m[8] * z + m[12],
81
+ m[1] * x + m[5] * y + m[9] * z + m[13],
82
+ m[2] * x + m[6] * y + m[10] * z + m[14],
83
+ ];
84
+ }
85
+
86
+ /**
87
+ * Invert a column-major 4×4 that is an affine transform — which every node transform in this engine
88
+ * is. General enough for a `T · R · S` with non-uniform scale, and not for a projection.
89
+ *
90
+ * @param {ArrayLike<number>} m
91
+ * @returns {Float64Array}
92
+ */
93
+ function affine_inverse(m) {
94
+ const a = m[0], b = m[4], c = m[8];
95
+ const d = m[1], e = m[5], f = m[9];
96
+ const g = m[2], h = m[6], i = m[10];
97
+
98
+ const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
99
+
100
+ assert.notEqual(det, 0, 'transform is singular');
101
+
102
+ const s = 1 / det;
103
+
104
+ const r = new Float64Array(16);
105
+
106
+ r[0] = (e * i - f * h) * s;
107
+ r[1] = (f * g - d * i) * s;
108
+ r[2] = (d * h - e * g) * s;
109
+
110
+ r[4] = (c * h - b * i) * s;
111
+ r[5] = (a * i - c * g) * s;
112
+ r[6] = (b * g - a * h) * s;
113
+
114
+ r[8] = (b * f - c * e) * s;
115
+ r[9] = (c * d - a * f) * s;
116
+ r[10] = (a * e - b * d) * s;
117
+
118
+ const tx = m[12], ty = m[13], tz = m[14];
119
+
120
+ r[12] = -(r[0] * tx + r[4] * ty + r[8] * tz);
121
+ r[13] = -(r[1] * tx + r[5] * ty + r[9] * tz);
122
+ r[14] = -(r[2] * tx + r[6] * ty + r[10] * tz);
123
+
124
+ r[15] = 1;
125
+
126
+ return r;
127
+ }
128
+
129
+ /**
130
+ * Contiguous runs of a sorted list of integers, as `[first, last]` pairs.
131
+ *
132
+ * @param {number[]} values
133
+ * @returns {number[][]}
134
+ */
135
+ function runs_of(values) {
136
+ const runs = [];
137
+
138
+ for (const value of values) {
139
+ const last = runs[runs.length - 1];
140
+
141
+ if (last !== undefined && last[1] === value - 1) {
142
+ last[1] = value;
143
+ } else {
144
+ runs.push([value, value]);
145
+ }
146
+ }
147
+
148
+ return runs;
149
+ }
150
+
151
+ /**
152
+ * The clone's deformed vertices, against `inverse(mesh.global) × rest`.
153
+ *
154
+ * Reads the GPU's own answer to "where does this geometry id's data live" rather than reconstructing
155
+ * it from the host records: the geometry metadata row, then the clone's meshlet metadata rows, then
156
+ * the data those rows address. That is the same chain the rasterizer and the intersector walk, so a
157
+ * clone whose *addressing* is wrong is caught here rather than being read around.
158
+ *
159
+ * @param {object} options
160
+ * @param {Renderer} options.renderer
161
+ * @param {Scene} options.scene
162
+ * @param {SkinnedMesh} options.mesh
163
+ * @param {number} [options.tolerance] in mesh-local units — the space the clone is written in,
164
+ * which for a glTF character carrying a centimetre scale is a hundred times its world size
165
+ * @returns {Promise<object>}
166
+ */
167
+ export async function verify_clone_vertices({ renderer, scene, mesh, tolerance = 1e-3 }) {
168
+ assert.defined(mesh, 'mesh');
169
+ assert.equal(mesh.isSkinnedMesh, true, 'mesh.isSkinnedMesh !== true');
170
+
171
+ const graphics = renderer.graphics;
172
+ const device = graphics.device;
173
+ const geometries = graphics.geometries;
174
+
175
+ const scene_context = renderer.scenes.obtain(scene);
176
+
177
+ const geometry_index = scene_context.skinning.obtain_geometry_index(mesh);
178
+
179
+ // ---- where the clone's meshlets are, as the GPU has it -------------------------------------
180
+
181
+ const geometry_row = new DataView(await read_back(
182
+ device,
183
+ geometries.buffer_metadata,
184
+ geometry_index * GEOMETRY_METADATA_STRUCT.size,
185
+ GEOMETRY_METADATA_STRUCT.size
186
+ ));
187
+
188
+ const meshlets_address = geometry_row.getUint32(
189
+ GEOMETRY_METADATA_STRUCT.get('meshlets_address').offset, true
190
+ );
191
+ const meshlets_count = geometry_row.getUint32(
192
+ GEOMETRY_METADATA_STRUCT.get('meshlets_count').offset, true
193
+ );
194
+
195
+ const source_batch = mesh.geometry.meshlets;
196
+
197
+ if (meshlets_count !== source_batch.count) {
198
+ return {
199
+ geometry_index,
200
+ verdict: `FAIL — the clone's geometry row claims ${meshlets_count} meshlets, `
201
+ + `its source has ${source_batch.count}`,
202
+ };
203
+ }
204
+
205
+ const meshlet_stride = MESHLET_METADATA_STRUCT.aligned_size;
206
+
207
+ const clone_metadata = new DataView(await read_back(
208
+ device,
209
+ geometries.meshlets.buffer_metadata,
210
+ meshlets_address * meshlet_stride,
211
+ meshlets_count * meshlet_stride
212
+ ));
213
+
214
+ const ADDRESS_OFFSET = MESHLET_METADATA_STRUCT.get('address').offset;
215
+ const VERTEX_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('vertex_count').offset;
216
+ const PRIMITIVE_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('primitive_count').offset;
217
+
218
+ // ---- and where the source's are, to say the clone is somewhere else --------------------------
219
+
220
+ /*
221
+ A clone is a fresh vertex region plus a patch pass that adds `clone.data.offset -
222
+ source.data.offset` to every one of its meshlet records. If that patch does not land, the
223
+ clone's records still address the *source's* data: the skinning pass then deforms the shared
224
+ rest-pose vertices in place, every instance of the model deforms on top of every other, and the
225
+ source no rasterizer ever wanted deformed goes with them. The addresses differing by one
226
+ constant is the whole contract, and it is checkable before a single vertex is compared.
227
+ */
228
+ const source_record = geometries.get_metadata(mesh.geometry);
229
+
230
+ const source_metadata = new DataView(await read_back(
231
+ device,
232
+ geometries.meshlets.buffer_metadata,
233
+ source_record.meshlets.metadata.offset * meshlet_stride,
234
+ meshlets_count * meshlet_stride
235
+ ));
236
+
237
+ // ---- the clone's vertex data, in one read ---------------------------------------------------
238
+
239
+ /*
240
+ Position is the first attribute of the section and is never collapsed to a shared slot (see
241
+ ENCODED_ORDERED_ATTRIBUTES and MESHLET_ATTRIBUTE_COMPRESSION_FLAGS), so a meshlet's positions
242
+ start where its attribute section does and the furthest word this needs is the last position
243
+ word of the furthest meshlet. That is a span, not a set of holes, so it comes back in one copy
244
+ rather than in `meshlets_count` of them — two hundred round trips through `mapAsync` is a
245
+ diagnostic nobody runs twice.
246
+ */
247
+ const position_word_of = new Uint32Array(meshlets_count);
248
+
249
+ let data_low = Infinity;
250
+ let data_high = 0;
251
+
252
+ /** every distinct `clone address − source address`; one entry is the contract */
253
+ const address_deltas = new Set();
254
+
255
+ for (let m = 0; m < meshlets_count; m++) {
256
+ const row = m * meshlet_stride;
257
+
258
+ const address = clone_metadata.getUint32(row + ADDRESS_OFFSET, true);
259
+ const primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
260
+ const vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
261
+
262
+ address_deltas.add(address - source_metadata.getUint32(row + ADDRESS_OFFSET, true));
263
+
264
+ const position_word = address + ((primitive_count * 3 + 3) >> 2);
265
+
266
+ position_word_of[m] = position_word;
267
+
268
+ data_low = Math.min(data_low, position_word);
269
+ data_high = Math.max(data_high, position_word + vertex_count * 3);
270
+ }
271
+
272
+ if (address_deltas.size !== 1 || address_deltas.has(0)) {
273
+ return {
274
+ geometry_index,
275
+ address_deltas: [...address_deltas].slice(0, 8),
276
+ verdict: address_deltas.has(0) && address_deltas.size === 1
277
+ ? `FAIL — every one of the clone's ${meshlets_count} meshlet records addresses the `
278
+ + `source's vertex data. The address patch did not run, so the skinning pass is `
279
+ + `deforming the shared rest-pose region in place.`
280
+ : `FAIL — the clone's meshlet records sit at ${address_deltas.size} different `
281
+ + `offsets from the source's; a clone is one contiguous region and its patch is `
282
+ + `one delta.`,
283
+ };
284
+ }
285
+
286
+ const data = new Float32Array(await read_back(
287
+ device,
288
+ geometries.meshlets.buffer_data,
289
+ data_low * 4,
290
+ (data_high - data_low) * 4
291
+ ));
292
+
293
+ // ---- the answer the CPU knows ---------------------------------------------------------------
294
+
295
+ const to_mesh_local = affine_inverse(mesh.transform_global.matrix);
296
+
297
+ // ---- and the comparison ---------------------------------------------------------------------
298
+
299
+ let vertices = 0;
300
+ let wrong = 0;
301
+ let worst = 0;
302
+ let worst_where = null;
303
+
304
+ /** meshlets with at least one wrong vertex */
305
+ const wrong_meshlets = [];
306
+
307
+ /** per-vertex-slot tally: how many meshlets got slot `v` wrong */
308
+ const wrong_by_slot = new Map();
309
+
310
+ const examples = [];
311
+
312
+ for (let m = 0; m < meshlets_count; m++) {
313
+ const source_header = source_batch.read_header(m);
314
+ const source = source_batch.read_meshlet(m);
315
+
316
+ const row = m * meshlet_stride;
317
+
318
+ const clone_vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
319
+ const clone_primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
320
+
321
+ if (clone_vertex_count !== source_header.vertex_count
322
+ || clone_primitive_count !== source_header.primitive_count) {
323
+ return {
324
+ geometry_index,
325
+ verdict: `FAIL — clone meshlet ${m} says `
326
+ + `${clone_vertex_count} vertices / ${clone_primitive_count} triangles, `
327
+ + `its source says ${source_header.vertex_count} / ${source_header.primitive_count}. `
328
+ + `The metadata copy or its address patch is wrong, before any skinning ran.`,
329
+ };
330
+ }
331
+
332
+ const positions = data.subarray(
333
+ position_word_of[m] - data_low,
334
+ position_word_of[m] - data_low + clone_vertex_count * 3
335
+ );
336
+
337
+ let meshlet_wrong = 0;
338
+
339
+ for (let v = 0; v < clone_vertex_count; v++) {
340
+ vertices++;
341
+
342
+ const expected = transform_point(
343
+ to_mesh_local,
344
+ source.attribute_position[v * 3],
345
+ source.attribute_position[v * 3 + 1],
346
+ source.attribute_position[v * 3 + 2],
347
+ );
348
+
349
+ const error = Math.max(
350
+ Math.abs(positions[v * 3] - expected[0]),
351
+ Math.abs(positions[v * 3 + 1] - expected[1]),
352
+ Math.abs(positions[v * 3 + 2] - expected[2]),
353
+ );
354
+
355
+ if (error > worst) {
356
+ worst = error;
357
+ worst_where = { meshlet: m, vertex: v };
358
+ }
359
+
360
+ if (error > tolerance) {
361
+ wrong++;
362
+ meshlet_wrong++;
363
+
364
+ wrong_by_slot.set(v, (wrong_by_slot.get(v) ?? 0) + 1);
365
+
366
+ if (examples.length < 8) {
367
+ examples.push({
368
+ meshlet: m,
369
+ vertex: v,
370
+ got: [positions[v * 3], positions[v * 3 + 1], positions[v * 3 + 2]],
371
+ expected,
372
+ });
373
+ }
374
+ }
375
+ }
376
+
377
+ if (meshlet_wrong > 0) {
378
+ wrong_meshlets.push(m);
379
+ }
380
+ }
381
+
382
+ // The two shapes worth telling apart, named rather than left to be read out of the runs: a
383
+ // stride-loop bug wrongs every slot at or past the workgroup size and nothing below it, and an
384
+ // addressing bug wrongs whole meshlets.
385
+ const slots = [...wrong_by_slot.keys()].sort((a, b) => a - b);
386
+
387
+ const lowest_wrong_slot = slots.length > 0 ? slots[0] : null;
388
+
389
+ return {
390
+ geometry_index,
391
+ meshlets: meshlets_count,
392
+ address_delta: [...address_deltas][0],
393
+ vertices,
394
+ wrong,
395
+ worst,
396
+ worst_where,
397
+ wrong_meshlets: wrong_meshlets.length,
398
+ wrong_meshlet_runs: runs_of(wrong_meshlets).slice(0, 20),
399
+ lowest_wrong_slot,
400
+ highest_correct_slot: slots.length > 0 ? lowest_wrong_slot - 1 : null,
401
+ wrong_slot_runs: runs_of(slots).slice(0, 20),
402
+ examples,
403
+ verdict: wrong === 0
404
+ ? `pass — all ${vertices} of the clone's vertices are the bind pose, worst error ${worst}`
405
+ : `FAIL — ${wrong} of ${vertices} vertices in ${wrong_meshlets.length} of `
406
+ + `${meshlets_count} meshlets are not the bind pose (worst ${worst}); `
407
+ + `lowest wrong vertex slot ${lowest_wrong_slot}`,
408
+ };
409
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"chunk_particle_cull.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/cull/chunk_particle_cull.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,kCAFU,SAAS,CAwBhB;0BAnCuB,oCAAoC"}
1
+ {"version":3,"file":"chunk_particle_cull.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/cull/chunk_particle_cull.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,kCAFU,SAAS,CA0BhB;0BArCuB,oCAAoC"}
@@ -11,7 +11,9 @@ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
11
11
  *
12
12
  * @type {CodeChunk}
13
13
  */
14
- export const chunk_particle_cull = CodeChunk.from(/* wgsl */ `
14
+ export const chunk_particle_cull = CodeChunk.from(
15
+ /* language=wgsl */
16
+ `
15
17
  // A sphere is on the inside (or straddling) a plane if its centre is no more than 'radius' behind it.
16
18
  fn particle_plane_sphere_visible(plane : vec4<f32>, center : vec3<f32>, radius : f32) -> bool {
17
19
  return dot(plane.xyz, center) + plane.w >= -radius;
@@ -13,7 +13,7 @@ const F = EMITTER_FIELD;
13
13
  *
14
14
  * @type {CodeChunk}
15
15
  */
16
- export const chunk_particle_emitter = CodeChunk.from(/* wgsl */ `
16
+ export const chunk_particle_emitter = CodeChunk.from(/* language=wgsl */ `
17
17
  const PARTICLE_EMITTER_STRIDE : u32 = ${EMITTER_STRIDE}u;
18
18
  const EMITTER_BLEND_SHIFT : u32 = ${EMITTER_FLAG_ENCODING.BLEND_SHIFT}u;
19
19
  const EMITTER_BLEND_MASK : u32 = ${EMITTER_FLAG_ENCODING.BLEND_MASK}u;
@@ -15,7 +15,7 @@
15
15
  * @param {ParticleAssembler} asm assembler owning the shared constant pool
16
16
  * @param {import("../layout/ParticleLayout.js").ParticleLayout} layout attribute schema
17
17
  */
18
- export function compile_particle_graph(graph: import("./ParticleGraph.js").ParticleGraph, stream: import("../isa/ParticleAssembler.js").InstructionStream, asm: ParticleAssembler, layout: import("../layout/ParticleLayout.js").ParticleLayout): void;
18
+ export function compile_particle_graph(graph: import("./ParticleGraph.js").ParticleGraph, stream: any, asm: ParticleAssembler, layout: import("../layout/ParticleLayout.js").ParticleLayout): void;
19
19
  /**
20
20
  * Compile a full effect (SPAWN + UPDATE) into a {@link ../isa/ParticleProgram.js}.
21
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"compile_particle_graph.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/compile_particle_graph.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,8CALW,OAAO,oBAAoB,EAAE,aAAa,UAC1C,OAAO,6BAA6B,EAAE,iBAAiB,OACvD,iBAAiB,UACjB,OAAO,6BAA6B,EAAE,cAAc,QAqD9D;AAED;;;;;;;;GAQG;AACH;IAL+D,KAAK,GAAzD,OAAO,oBAAoB,EAAE,aAAa;IACU,MAAM,GAA1D,OAAO,oBAAoB,EAAE,aAAa;IACmB,MAAM,EAAnE,OAAO,6BAA6B,EAAE,cAAc;IAClD,OAAO,2BAA2B,EAAE,eAAe,CAa/D;kCA7FiC,6BAA6B"}
1
+ {"version":3,"file":"compile_particle_graph.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/graph/compile_particle_graph.js"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;GAgBG;AACH,8CALW,OAAO,oBAAoB,EAAE,aAAa,oBAE1C,iBAAiB,UACjB,OAAO,6BAA6B,EAAE,cAAc,QAqD9D;AAED;;;;;;;;GAQG;AACH;IAL+D,KAAK,GAAzD,OAAO,oBAAoB,EAAE,aAAa;IACU,MAAM,GAA1D,OAAO,oBAAoB,EAAE,aAAa;IACmB,MAAM,EAAnE,OAAO,6BAA6B,EAAE,cAAc;IAClD,OAAO,2BAA2B,EAAE,eAAe,CAa/D;kCA7FiC,6BAA6B"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * A single VM instruction stream (a SPAWN or UPDATE section). Instructions are appended in order;
3
+ * {@link toWords} flattens them to the packed `Uint32Array` the program consumes.
4
+ *
5
+ * Register usage is tracked as instructions are emitted so the assembler can report how many
6
+ * registers the program touches without a separate pass.
7
+ */
8
+ export class InstructionStream {
9
+ /** @type {number[][]} */
10
+ instructions: number[][];
11
+ /** @type {number} highest register index referenced, plus one */
12
+ register_count: number;
13
+ /**
14
+ * Emit a raw instruction. Validates the opcode and tracks register usage. Prefer the typed
15
+ * helpers below where they exist — they document the non-obvious operand packing.
16
+ *
17
+ * @param {number} op
18
+ * @param {number} [dst]
19
+ * @param {number} [s0]
20
+ * @param {number} [s1]
21
+ * @param {number} [s2]
22
+ * @returns {InstructionStream} this, for chaining
23
+ */
24
+ emit(op: number, dst?: number, s0?: number, s1?: number, s2?: number): InstructionStream;
25
+ /**
26
+ * @param {number} dst
27
+ * @param {number} src
28
+ * @returns {InstructionStream}
29
+ */
30
+ mov(dst: number, src: number): InstructionStream;
31
+ /**
32
+ * @param {number} dst
33
+ * @param {number} constant_index
34
+ * @returns {InstructionStream}
35
+ */
36
+ loadConst(dst: number, constant_index: number): InstructionStream;
37
+ /**
38
+ * @param {number} dst
39
+ * @param {number} word_offset
40
+ * @param {number} component_count
41
+ * @returns {InstructionStream}
42
+ */
43
+ loadAttr(dst: number, word_offset: number, component_count: number): InstructionStream;
44
+ /**
45
+ * @param {number} src_register
46
+ * @param {number} word_offset
47
+ * @param {number} component_count
48
+ * @returns {InstructionStream}
49
+ */
50
+ storeAttr(src_register: number, word_offset: number, component_count: number): InstructionStream;
51
+ /**
52
+ * @param {number} dst
53
+ * @param {number} builtin_id
54
+ * @returns {InstructionStream}
55
+ */
56
+ loadBuiltin(dst: number, builtin_id: number): InstructionStream;
57
+ /**
58
+ * @param {number} condition_register
59
+ * @returns {InstructionStream}
60
+ */
61
+ kill(condition_register: number): InstructionStream;
62
+ /**
63
+ * @param {number} dst
64
+ * @param {number} mode
65
+ * @returns {InstructionStream}
66
+ */
67
+ random(dst: number, mode: number): InstructionStream;
68
+ /**
69
+ * @param {number} dst
70
+ * @param {number} curve_handle
71
+ * @param {number} t_register
72
+ * @returns {InstructionStream}
73
+ */
74
+ curve(dst: number, curve_handle: number, t_register: number): InstructionStream;
75
+ /**
76
+ * @returns {InstructionStream}
77
+ */
78
+ halt(): InstructionStream;
79
+ /**
80
+ * @returns {number}
81
+ */
82
+ get instruction_count(): number;
83
+ /**
84
+ * @returns {Uint32Array}
85
+ */
86
+ toWords(): Uint32Array;
87
+ #private;
88
+ }
89
+ //# sourceMappingURL=InstructionStream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InstructionStream.d.ts","sourceRoot":"","sources":["../../../../../../src/shade/renderer/particles/isa/InstructionStream.js"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH;IACI,yBAAyB;IACzB,cADW,MAAM,EAAE,EAAE,CACH;IAClB,iEAAiE;IACjE,gBADW,MAAM,CACE;IAUnB;;;;;;;;;;OAUG;IACH,SAPW,MAAM,QACN,MAAM,OACN,MAAM,OACN,MAAM,OACN,MAAM,GACJ,iBAAiB,CAsB7B;IAID;;;;OAIG;IACH,SAJW,MAAM,OACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,eAJW,MAAM,kBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,cALW,MAAM,eACN,MAAM,mBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,wBALW,MAAM,eACN,MAAM,mBACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,iBAJW,MAAM,cACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;OAGG;IACH,yBAHW,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;OAIG;IACH,YAJW,MAAM,QACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;;;;OAKG;IACH,WALW,MAAM,gBACN,MAAM,cACN,MAAM,GACJ,iBAAiB,CAI7B;IAED;;OAEG;IACH,QAFa,iBAAiB,CAI7B;IAED;;OAEG;IACH,gCAEC;IAED;;OAEG;IACH,WAFa,WAAW,CAWvB;;CACJ"}