reze-engine 0.54.7 → 0.54.9

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.
@@ -0,0 +1,288 @@
1
+ import { EFFECT_SUBJECTS } from "../cast-layout";
2
+ // How far is this pixel from the cast? — the primitive behind every silhouette
3
+ // look, computed once and read by anyone.
4
+ //
5
+ // AN EFFECT CANNOT ANSWER THIS ITSELF, and that is the whole reason this pass
6
+ // exists. Distance to the nearest drawn pixel is not a function of the pixel;
7
+ // it is a function of every pixel around it. A shader with one pass can only go
8
+ // looking, and searching a disc costs O(radius^2) per pixel — measured on the
9
+ // first sticker-outline effect, a 9 pixel border cost 64 id samples on every
10
+ // background pixel and a 16 pixel one cost 96 to 128. The look everybody wants
11
+ // next is a wide one, and that is the direction the cost runs away in.
12
+ //
13
+ // A JUMP FLOOD costs log2(resolution) passes and answers for ANY distance. It
14
+ // is the same 37 million reads at 1080p whether the effect wants a 4 pixel
15
+ // border or a 400 pixel aura, and it is shared: two effects reading it pay for
16
+ // it once.
17
+ //
18
+ // NO CAP ON THE DISTANCE. A partial flood — stopping early to save a pass —
19
+ // would silently limit how far an effect could reach, and the engine has no
20
+ // business deciding that. The author writes the falloff and pays for the width
21
+ // they asked for in their own arithmetic, not in ours.
22
+ //
23
+ // FULL RESOLUTION, after trying not to be.
24
+ //
25
+ // Half res was tried twice and rejected on sight both times, and the reason is
26
+ // worth recording because the arithmetic looked fine. A half-res distance field
27
+ // really does place the edge to within a pixel — measured against an exact
28
+ // transform, 0.22 px mean and 0.68 px at the 99th percentile. What that average
29
+ // hides is that the error is not noise: it VARIES along the silhouette, so the
30
+ // border breathes in and out by a fraction of a pixel as the edge turns, and an
31
+ // edge that wobbles reads as jagged no matter how smoothly it is feathered.
32
+ // Seeding at the sub-texel centroid rather than the texel centre halved it
33
+ // (0.83 to 0.28 px mean on a curve) and it was still visible.
34
+ //
35
+ // So the field is the size of the frame and the seeds are exact. What that costs
36
+ // at 1080p is about 224 million texture reads a frame, against 47 million at
37
+ // half — real, and the honest price of an edge that does not crawl. It is still
38
+ // the same cost whatever width an effect asks for, which is the whole point: the
39
+ // per-pixel search this replaced cost 64 reads a pixel for a 9 px border and
40
+ // four times that for an 18 px one.
41
+ /** Whether any of this effect's source reads the field, and so whether the
42
+ * engine should spend the passes building it. Nothing else turns it on. */
43
+ export function castDistanceUsed(wgsl) {
44
+ return /\brzCastDistance\s*\(/.test(wgsl);
45
+ }
46
+ /** Seed and ping-pong target: the nearest seed's texel coordinate, or (-1,-1).
47
+ * 32-bit because these are coordinates — a half would quantise them at 2048
48
+ * and put the seam of that error straight through a 4K frame. */
49
+ export const CAST_SEED_FORMAT = "rg32float";
50
+ /** The resolved distance, in FIELD texels. Sampled bilinearly, so it is filtered
51
+ * rather than loaded, and r32float is not filterable everywhere — r16float is,
52
+ * and half a texel of precision on a distance is nothing. */
53
+ export const CAST_DIST_FORMAT = "r16float";
54
+ /** Per-pixel MSAA coverage of the cast, written beside the seeds and read by the
55
+ * resolve to place the edge inside the seed pixel. */
56
+ export const CAST_COVERAGE_FORMAT = "r8unorm";
57
+ /** The field is the size of the frame. See the note above for what half cost. */
58
+ export const CAST_FIELD_DIV = 1;
59
+ const FULLSCREEN_VS = /* wgsl */ `
60
+ struct VSOut { @builtin(position) pos: vec4f };
61
+
62
+ @vertex
63
+ fn vs(@builtin(vertex_index) i: u32) -> VSOut {
64
+ // One oversized triangle rather than two: no seam down the diagonal, and the
65
+ // rasteriser does not have to think about a shared edge.
66
+ var p = array<vec2f, 3>(vec2f(-1.0, -3.0), vec2f(-1.0, 1.0), vec2f(3.0, 1.0));
67
+ var o: VSOut;
68
+ o.pos = vec4f(p[i], 0.0, 1.0);
69
+ return o;
70
+ }
71
+ `;
72
+ /**
73
+ * Pass 1 — plant a seed on every pixel the CAST drew.
74
+ *
75
+ * The ground, a stage and a media plane all write ids exactly as she does, so
76
+ * seeding "anything drawn" would grow a border around the floor: a rectangle
77
+ * round the frame rather than a sticker. Only the subject ids seed.
78
+ */
79
+ export function buildCastSeedShader(samples) {
80
+ return (
81
+ /* wgsl */ `
82
+ const RZ_SUBJECTS: i32 = ${EFFECT_SUBJECTS};
83
+
84
+ const RZ_ID_SAMPLES: i32 = ${samples};
85
+
86
+ @group(0) @binding(0) var _rzIdTex: texture_multisampled_2d<u32>;
87
+ @group(0) @binding(1) var<storage, read> _rzCast: array<vec4f>;
88
+
89
+ // The two cast accessors this pass needs, spelled out rather than pulled in.
90
+ // CAST_API brings subjects, trails, anchors and their aliases with it, and a
91
+ // seed pass wants none of that — it asks one question about one id.
92
+ fn rzSubjectCount() -> i32 {
93
+ var n = 0;
94
+ for (var i = 0; i < RZ_SUBJECTS; i++) {
95
+ if (_rzCast[i * 3 + 2].w > 0.0) { n = i + 1; }
96
+ }
97
+ return n;
98
+ }
99
+ fn rzSubjectId(i: i32) -> u32 {
100
+ if (i < 0 || i >= rzSubjectCount()) { return 0u; }
101
+ return u32(_rzCast[i * 3 + 1].w);
102
+ }
103
+
104
+ ${FULLSCREEN_VS}
105
+
106
+ struct SeedOut {
107
+ /** Where the nearest cast pixel is, for the flood to carry. */
108
+ @location(0) seed: vec2f,
109
+ /** How much of this pixel she covers, 0..1, for the resolve to place the edge
110
+ * inside it. */
111
+ @location(1) coverage: f32,
112
+ }
113
+
114
+ @fragment
115
+ fn fs(@builtin(position) pos: vec4f) -> SeedOut {
116
+ // COVERAGE, NOT A YES OR NO — this is the whole reason the border used to sit
117
+ // badly against her.
118
+ //
119
+ // The scene draws at 4x MSAA and the frame you see is resolved, so her edge is
120
+ // smooth. The id attachment is deliberately NOT resolved (an averaged id
121
+ // belongs to nothing), so reading sample 0 gives a hard, binary, aliased
122
+ // silhouette — a different edge from the one she is drawn with. A border grown
123
+ // off that can never sit flush against her: it traces the staircase while she
124
+ // has none.
125
+ //
126
+ // Counting the samples recovers what MSAA already knew. Four samples give five
127
+ // levels of coverage, the resolve turns that into a sub-pixel zero crossing,
128
+ // and the border meets her where she actually ends.
129
+ let dim = vec2<i32>(textureDimensions(_rzIdTex));
130
+ let p = clamp(vec2<i32>(pos.xy), vec2<i32>(0), dim - vec2<i32>(1));
131
+ let n = rzSubjectCount();
132
+ var covered = 0.0;
133
+ for (var sample = 0; sample < RZ_ID_SAMPLES; sample++) {
134
+ let o = textureLoad(_rzIdTex, p, sample).y;
135
+ if (o == 0u) { continue; }
136
+ for (var i = 0; i < n; i++) {
137
+ if (o == rzSubjectId(i)) { covered += 1.0; break; }
138
+ }
139
+ }
140
+ var out: SeedOut;
141
+ out.coverage = covered / f32(RZ_ID_SAMPLES);
142
+ // Anything she touches at all seeds, including a pixel she barely clips: a
143
+ // sliver is where the sub-pixel edge lives, and dropping it would put the
144
+ // staircase straight back.
145
+ // -1 is the empty marker, and every step below tests for it before believing
146
+ // a candidate.
147
+ out.seed = select(vec2f(-1.0, -1.0), pos.xy, covered > 0.0);
148
+ return out;
149
+ }
150
+ `);
151
+ }
152
+ /**
153
+ * Pass 2 — one jump-flood step, run once per halving of the stride.
154
+ *
155
+ * Each texel asks its eight neighbours at the current stride what seed THEY
156
+ * know about, and keeps whichever is nearest to itself. Starting at half the
157
+ * field's width and halving to one, a seed reaches every texel that is nearer
158
+ * to it than to any other — which is the definition of the field.
159
+ */
160
+ export function buildCastStepShader() {
161
+ return /* wgsl */ `
162
+ @group(0) @binding(0) var _rzPrev: texture_2d<f32>;
163
+ @group(0) @binding(1) var<uniform> _rzStep: vec4f; // (stride, _, _, _)
164
+
165
+ ${FULLSCREEN_VS}
166
+
167
+ @fragment
168
+ fn fs(@builtin(position) pos: vec4f) -> @location(0) vec2f {
169
+ let me = pos.xy;
170
+ let dim = vec2<i32>(textureDimensions(_rzPrev));
171
+ let stride = i32(_rzStep.x);
172
+ var best = textureLoad(_rzPrev, vec2<i32>(me), 0).xy;
173
+ // Squared throughout: the comparison is the only thing that matters and a
174
+ // square root per candidate is nine of them per texel per pass.
175
+ var bestD = select(1.0e30, dot(best - me, best - me), best.x >= 0.0);
176
+ for (var dy = -1; dy <= 1; dy++) {
177
+ for (var dx = -1; dx <= 1; dx++) {
178
+ let p = clamp(vec2<i32>(me) + vec2<i32>(dx, dy) * stride, vec2<i32>(0), dim - vec2<i32>(1));
179
+ let c = textureLoad(_rzPrev, p, 0).xy;
180
+ if (c.x < 0.0) { continue; }
181
+ let d = dot(c - me, c - me);
182
+ if (d < bestD) { bestD = d; best = c; }
183
+ }
184
+ }
185
+ return best;
186
+ }
187
+ `;
188
+ }
189
+ /**
190
+ * Pass 3 — turn the seed coordinates into a distance.
191
+ *
192
+ * Its own pass because the READ is bilinear. Interpolating coordinates across
193
+ * the boundary between two seeds averages two unrelated points and lands the
194
+ * result on neither; interpolating the DISTANCE is the smooth, honest thing,
195
+ * and it is what makes a half-res field cut a full-res edge.
196
+ */
197
+ export function buildCastResolveShader() {
198
+ return /* wgsl */ `
199
+ @group(0) @binding(0) var _rzSeed: texture_2d<f32>;
200
+ @group(0) @binding(1) var _rzCoverage: texture_2d<f32>;
201
+
202
+ ${FULLSCREEN_VS}
203
+
204
+ @fragment
205
+ fn fs(@builtin(position) pos: vec4f) -> @location(0) f32 {
206
+ let s = textureLoad(_rzSeed, vec2<i32>(pos.xy), 0).xy;
207
+ // No cast on screen at all: everything is unreachably far from her, which is
208
+ // the answer that makes an effect draw nothing rather than everything.
209
+ if (s.x < 0.0) { return 1.0e30; }
210
+ let c = textureLoad(_rzCoverage, vec2<i32>(s), 0).x;
211
+ // SIGNED, WITH THE ZERO CROSSING INSIDE THE SEED PIXEL.
212
+ //
213
+ // A fully covered seed means her true edge runs about half a pixel outside its
214
+ // centre, so the distance to the EDGE is half a pixel less than the distance to
215
+ // the centre. A half-covered seed has the edge through its centre and wants no
216
+ // correction. A barely covered one has the edge nearly a half-pixel further in.
217
+ // All three are (c - 0.5).
218
+ //
219
+ // It also makes the field NEGATIVE inside her, and that is what lets an effect
220
+ // fade where it meets her rather than stopping dead on a texel boundary — the
221
+ // difference between a border that is attached and one that is merely nearby.
222
+ return length(s - pos.xy) - (c - 0.5);
223
+ }
224
+ `;
225
+ }
226
+ /**
227
+ * `rzCastDistance(uv)`, for the effects that read it.
228
+ *
229
+ * ALWAYS COMPILED IN, even when the pass is not running — it then samples a 1x1
230
+ * holding a very large number, so an effect keyed on it draws nothing instead of
231
+ * failing to compile. Same rule the grid's accessor follows, and the id
232
+ * accessors before it: an author should never have to guard a name.
233
+ */
234
+ export function castDistanceApi(group, tex, samp, scale) {
235
+ return /* wgsl */ `
236
+ @group(${group}) @binding(${tex}) var _rzCastDistTex: texture_2d<f32>;
237
+ @group(${group}) @binding(${samp}) var _rzCastDistSamp: sampler;
238
+
239
+ /**
240
+ * Distance from this point to the nearest pixel the CAST drew, in SCREEN pixels.
241
+ *
242
+ * ZERO ON HER, positive outside, and the crossing is sub-pixel — it comes from
243
+ * MSAA coverage, so it lands on the same edge she is drawn with rather than on
244
+ * the staircase a single sample sees. Fade an effect across zero and it attaches
245
+ * to her cleanly.
246
+ *
247
+ * IT IS NOT AN INTERIOR DEPTH. Every pixel she covers is a seed, so the nearest
248
+ * seed to a pixel deep inside her is itself: the answer there is 0, not "far
249
+ * in". What the coverage buys is half a pixel of sub-pixel placement at the
250
+ * boundary, which is exactly enough to anti-alias against her and no more — so
251
+ * the value inside her runs to -0.5 and no further. An effect that wants to
252
+ * reach INTO her wants a second flood seeded on the background, which this pass
253
+ * does not build. Guard on d >= 0 and fade across it.
254
+ *
255
+ * There is no ceiling. A pixel on the far side of the frame gets the real
256
+ * number. The ground, a stage and a media plane are not the cast and do not
257
+ * seed it.
258
+ *
259
+ * Screen pixels whatever the field's own resolution is, so an author writes the
260
+ * width they mean and never has to know how this is built. uv is the effect's
261
+ * own convention, origin bottom-left; the flip to texture rows happens here.
262
+ */
263
+ fn rzCastDistance(uv: vec2f) -> f32 {
264
+ let flipped = vec2f(clamp(uv.x, 0.0, 1.0), 1.0 - clamp(uv.y, 0.0, 1.0));
265
+ let d = textureSampleLevel(_rzCastDistTex, _rzCastDistSamp, flipped, 0.0).x;
266
+ return d * ${scale > 0 ? scale : 1}.0;
267
+ }
268
+ `;
269
+ }
270
+ /**
271
+ * The accessor as a STUB, for every module that is not a field.
272
+ *
273
+ * An effect's whole source is spliced into each module its entry points reach —
274
+ * declare lightEmit and the light module compiles your foreground() along with
275
+ * it. So a name that exists only in the field module is a compile error the
276
+ * moment one effect uses both, which is exactly what a silhouette glow that
277
+ * also lights the cast wants to do.
278
+ *
279
+ * Answers "unreachably far", the same shape of answer idApi gives when the id
280
+ * buffer is off: the arithmetic still runs and produces nothing, rather than the
281
+ * shader failing to build. Nothing outside a field mount has a screen position
282
+ * to ask about anyway.
283
+ */
284
+ export function castDistanceStub() {
285
+ return /* wgsl */ `
286
+ fn rzCastDistance(uv: vec2f) -> f32 { return 1.0e30; }
287
+ `;
288
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAgC+B;AAC/B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAIhG;AAID,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAEzG,KAAK,qBAAqB,GAAG;IAC3B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;IACtB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;yEAGqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB;;8EAE0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AA6LD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,yqaA+H5B,CAAA;AA4LD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAQlF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CA4EtE;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
1
+ {"version":3,"file":"composite.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/composite.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;+BAgC+B;AAC/B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAIhG;AAID,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAEzG,KAAK,qBAAqB,GAAG;IAC3B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;IACZ,kFAAkF;IAClF,UAAU,EAAE,MAAM,CAAA;IAClB,4DAA4D;IAC5D,aAAa,EAAE,OAAO,CAAA;IACtB,oEAAoE;IACpE,aAAa,EAAE,OAAO,CAAA;IACtB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAA;IAChB;mFAC+E;IAC/E,GAAG,CAAC,EAAE,OAAO,CAAA;IACb;;;yEAGqE;IACrE,UAAU,EAAE,MAAM,CAAA;IAClB;;8EAE0E;IAC1E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CACjB,CAAA;AA6LD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,yqaA+H5B,CAAA;AA4LD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,GAAG,MAAM,CAQlF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAgFtE;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,QAA6B,CAAA"}
@@ -4,6 +4,7 @@ import { CAST_API } from "../cast-api";
4
4
  import { clockApi, EFFECT_MATH_API, PARTICLE_STRUCT_WGSL, trailSlotsApi, viewportApi } from "./hosted-api";
5
5
  import { audioApi } from "../audio-api";
6
6
  import { idApi } from "../id-api";
7
+ import { castDistanceApi, CAST_FIELD_DIV } from "./cast-distance";
7
8
  import { lyricsApi, lyricsTextApi } from "../lyrics-api";
8
9
  import { midiApi } from "../midi-api";
9
10
  import { gridReadApi } from "./grid";
@@ -633,6 +634,10 @@ export function buildFieldShader(effect) {
633
634
  viewportApi("viewU[6].w") +
634
635
  trailSlotsApi(effect.trailCount) +
635
636
  idApi(effect.ids === true, 0, 23) +
637
+ // Distance to the cast, in SCREEN pixels: the field is half-res, so a field
638
+ // texel is CAST_FIELD_DIV of them and the accessor scales on the way out.
639
+ // An author writes the width they mean and never learns how it is built.
640
+ castDistanceApi(0, 26, 18, CAST_FIELD_DIV) +
636
641
  "\n// ── user effect (setEffect) ──\n" +
637
642
  effect.paramsDecl +
638
643
  "\n" +
@@ -1 +1 @@
1
- {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAkC7C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B1F;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAgEnF"}
1
+ {"version":3,"file":"grid.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/grid.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAkC7C,eAAO,MAAM,QAAQ,OAAO,CAAA;AAC5B,uEAAuE;AACvE,eAAO,MAAM,UAAU,EAAE,gBAAgC,CAAA;AAEzD,gDAAgD;AAChD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CA6B1F;AAWD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAgEnF"}
@@ -1,3 +1,4 @@
1
+ import { castDistanceStub } from "./cast-distance";
1
2
  import { audioApi } from "../audio-api";
2
3
  import { lyricsApi } from "../lyrics-api";
3
4
  import { anchorAliasWgsl } from "../anchor-table";
@@ -145,7 +146,7 @@ fn rzGrid(uv: vec2f) -> vec4f { return rzGridPrev(uv); }
145
146
  ` +
146
147
  // Stubbed: this module cannot read an attachment the scene pass writes — see
147
148
  // id-api.ts. The author's whole file compiles here, so the names must exist.
148
- idApi(false, 0, 0) +
149
+ idApi(false, 0, 0) + castDistanceStub() +
149
150
  "\n// ── user effect (setEffect) ──\n" +
150
151
  wgsl +
151
152
  /* wgsl */ `
@@ -1 +1 @@
1
- {"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AA0BA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAuDjC,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA6CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA4HvF"}
1
+ {"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/particles.ts"],"names":[],"mappings":"AA2BA,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAA;IACb;;mFAE+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,+EAA+E;IAC/E,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB,CAAA;AAiCD,qDAAqD;AACrD,KAAK,aAAa,GAAG,OAAO,GAAG,UAAU,CAAA;AAEzC,KAAK,cAAc,GAAG;IACpB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,aAAa,CAAA;IACpB,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED,oEAAoE;AACpE,eAAO,MAAM,eAAe,KAAK,CAAA;AAuDjC,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAMlG;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA6CxF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CA4HvF"}
@@ -1,3 +1,4 @@
1
+ import { castDistanceStub } from "./cast-distance";
1
2
  import { RZ_LIGHT_STRUCT_WGSL } from "../lights";
2
3
  import { audioApi } from "../audio-api";
3
4
  import { lyricsApi } from "../lyrics-api";
@@ -121,7 +122,7 @@ export function buildParticleComputeShader(src, cast) {
121
122
  PRELUDE +
122
123
  // Stubbed: this module cannot read an attachment the scene pass writes — see
123
124
  // id-api.ts. The author's whole file compiles here, so the names must exist.
124
- idApi(false, 0, 0) +
125
+ idApi(false, 0, 0) + castDistanceStub() +
125
126
  "\n// ── user effect ──\n" +
126
127
  src.wgsl +
127
128
  /* wgsl */ `
@@ -180,7 +181,7 @@ override ADDITIVE: bool = ${src.blend === "additive" ? "true" : "false"};\n` +
180
181
  PRELUDE +
181
182
  // Stubbed: this module cannot read an attachment the scene pass writes — see
182
183
  // id-api.ts. The author's whole file compiles here, so the names must exist.
183
- idApi(false, 0, 0) +
184
+ idApi(false, 0, 0) + castDistanceStub() +
184
185
  "\n// ── user effect ──\n" +
185
186
  src.wgsl +
186
187
  /* wgsl */ `
@@ -1 +1 @@
1
- {"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AA0BA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CAgVR"}
1
+ {"version":3,"file":"trails.d.ts","sourceRoot":"","sources":["../../../src/shaders/passes/trails.ts"],"names":[],"mappings":"AA2BA,KAAK,WAAW,GAAG;IACjB,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAA;IACb;oEACgE;IAChE,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,8DAA8D;IAC9D,KAAK,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,IAAI,CAAA;AAEnC,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAKjF;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE;IACJ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,EAAE,CAAA;IACf;;;;kFAI8E;IAC9E,SAAS,EAAE,OAAO,CAAA;CACnB,GACA,MAAM,CAgVR"}
@@ -1,3 +1,4 @@
1
+ import { castDistanceStub } from "./cast-distance";
1
2
  import { RZ_LIGHT_STRUCT_WGSL } from "../lights";
2
3
  import { audioApi } from "../audio-api";
3
4
  import { lyricsApi } from "../lyrics-api";
@@ -186,7 +187,7 @@ fn rzTurnRadius(a: vec3f, b: vec3f, c: vec3f) -> f32 {
186
187
  lyricsApi(0, 6) +
187
188
  // Stubbed: this module cannot read an attachment the scene pass writes — see
188
189
  // id-api.ts. The author's whole file compiles here, so the names must exist.
189
- idApi(false, 0, 0) +
190
+ idApi(false, 0, 0) + castDistanceStub() +
190
191
  "\n// ── user effect ──\n" +
191
192
  src.wgsl +
192
193
  /* wgsl */ `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.54.7",
3
+ "version": "0.54.9",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",