@dopaminefx/effect-lightning 0.1.0

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 @@
1
+ {"version":3,"file":"lightning-shader.js","sourceRoot":"","sources":["../src/lightning-shader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,cAAc,EACd,WAAW,EACX,QAAQ,EACR,SAAS,EACT,WAAW,EACX,iBAAiB,GAClB,MAAM,kBAAkB,CAAC;AAC1B,uEAAuE;AACvE,0EAA0E;AAC1E,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEvF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,CAAC;AAE3D;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,GAAG,cAAc,CAAC;AAEtD,MAAM,CAAC,MAAM,oBAAoB,GAAG,UAAU,CAAC;;;;EAI7C,CAAC;AAEH,MAAM,CAAC,MAAM,sBAAsB,GAAG,UAAU,CAAC;;;;;;;;;;;;;;;;;;;;;;;uBAuB1B,WAAW;0BACR,SAAS;;oBAEf,SAAS;oBACT,SAAS;oBACT,SAAS;cACf,cAAc;EAC1B,cAAc;EACd,SAAS;EACT,QAAQ;EACR,iBAAiB;EACjB,WAAW;EACX,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoIX,CAAC"}
@@ -0,0 +1,495 @@
1
+ {
2
+ "fmt": "dopamine-effect",
3
+ "v": "1.0.0",
4
+ "id": "dopamine.boost.lightning",
5
+ "meta": {
6
+ "name": "Lightning",
7
+ "description": "A high-energy power-up / boost strike: a branching, fbm-perturbed electric arc cracks into the action point with a hard white flash, secondary forks, and a brief flicker afterglow. Electric OKLCH blues/violets to a hot white core.",
8
+ "tags": [
9
+ "boost",
10
+ "power-up",
11
+ "electric",
12
+ "lightning",
13
+ "anchored"
14
+ ]
15
+ },
16
+ "controls": {
17
+ "mood": {
18
+ "type": "enum",
19
+ "label": "Mood",
20
+ "default": "electric",
21
+ "options": [
22
+ "serene",
23
+ "celebratory",
24
+ "electric"
25
+ ],
26
+ "ui": "segmented"
27
+ },
28
+ "intensity": {
29
+ "type": "scalar",
30
+ "label": "Intensity",
31
+ "default": 0.85,
32
+ "min": 0,
33
+ "max": 1,
34
+ "step": 0.01,
35
+ "ui": "slider",
36
+ "help": "Bolt thickness, branch count, flash brightness."
37
+ },
38
+ "whimsy": {
39
+ "type": "scalar",
40
+ "label": "Whimsy",
41
+ "default": 0.5,
42
+ "min": 0,
43
+ "max": 1,
44
+ "step": 0.01,
45
+ "ui": "slider",
46
+ "help": "Photoreal plasma glow (0) to flat cel comic-book bolt + harder strobe (1)."
47
+ },
48
+ "seed": {
49
+ "type": "int",
50
+ "label": "Seed",
51
+ "default": null,
52
+ "nullable": true,
53
+ "help": "Null = unique bolt + palette per fire; pin to reproduce."
54
+ },
55
+ "origin": {
56
+ "type": "point",
57
+ "label": "Origin",
58
+ "default": "center"
59
+ },
60
+ "target": {
61
+ "type": "selector",
62
+ "label": "Target",
63
+ "default": "document.body"
64
+ }
65
+ },
66
+ "baselines": {
67
+ "serene": {
68
+ "durationMs": 1400,
69
+ "lightness": 0.82,
70
+ "chroma": 0.12,
71
+ "hueCenter": 245,
72
+ "hueRange": 60,
73
+ "thickness": 0.012,
74
+ "jagged": 0.55,
75
+ "branches": 0,
76
+ "flashBright": 0.5,
77
+ "flicker": 0.3,
78
+ "overshoot": 0.7
79
+ },
80
+ "celebratory": {
81
+ "durationMs": 1100,
82
+ "lightness": 0.8,
83
+ "chroma": 0.18,
84
+ "hueCenter": 228,
85
+ "hueRange": 110,
86
+ "thickness": 0.016,
87
+ "jagged": 0.85,
88
+ "branches": 3,
89
+ "flashBright": 0.8,
90
+ "flicker": 0.65,
91
+ "overshoot": 1.05
92
+ },
93
+ "electric": {
94
+ "durationMs": 850,
95
+ "lightness": 0.8,
96
+ "chroma": 0.24,
97
+ "hueCenter": 268,
98
+ "hueRange": 90,
99
+ "thickness": 0.02,
100
+ "jagged": 1.15,
101
+ "branches": 6,
102
+ "flashBright": 1.1,
103
+ "flicker": 1,
104
+ "overshoot": 1.5
105
+ }
106
+ },
107
+ "palette": {
108
+ "model": "oklch",
109
+ "space": "linear-srgb",
110
+ "generator": "golden-angle",
111
+ "goldenAngleDeg": 137.50776405003785,
112
+ "stops": 3,
113
+ "hueSpread": 0.4,
114
+ "lightness": {
115
+ "baseline": "lightness",
116
+ "perStop": [
117
+ 0.08,
118
+ 0,
119
+ -0.06
120
+ ]
121
+ },
122
+ "chroma": {
123
+ "from": {
124
+ "mul": [
125
+ {
126
+ "baseline": "chroma"
127
+ },
128
+ {
129
+ "lerp": [
130
+ "intensity",
131
+ 0.7,
132
+ 1.4
133
+ ]
134
+ }
135
+ ]
136
+ },
137
+ "perStop": [
138
+ -0.02,
139
+ 0.02,
140
+ 0.01
141
+ ]
142
+ },
143
+ "seed": {
144
+ "deterministic": true,
145
+ "source": "controls.seed",
146
+ "prng": "mulberry32"
147
+ },
148
+ "perMood": {
149
+ "serene": {
150
+ "hueCenter": 245,
151
+ "hueRange": 60,
152
+ "lightness": 0.82,
153
+ "chroma": 0.12
154
+ },
155
+ "celebratory": {
156
+ "hueCenter": 228,
157
+ "hueRange": 110,
158
+ "lightness": 0.8,
159
+ "chroma": 0.18
160
+ },
161
+ "electric": {
162
+ "hueCenter": 268,
163
+ "hueRange": 90,
164
+ "lightness": 0.8,
165
+ "chroma": 0.24
166
+ }
167
+ }
168
+ },
169
+ "tempo": {
170
+ "durationMs": {
171
+ "from": {
172
+ "round": {
173
+ "mul": [
174
+ {
175
+ "baseline": "durationMs"
176
+ },
177
+ {
178
+ "lerp": [
179
+ "intensity",
180
+ 1.15,
181
+ 0.9
182
+ ]
183
+ }
184
+ ]
185
+ }
186
+ }
187
+ },
188
+ "frame": {
189
+ "note": "The per-frame logic, datafied (was lightning-tempo): amp = the impact envelope; strike = strikeProgress(animMs), the ease-out-quint crack-in over the 130 ms STRIKE_MS window (the same clock the CPU bolt precompute keys off — see x-build.logic); flash = flashStrobe(life, flicker), an instantaneous near-white primary that decays fast (exp(-t/0.035)) plus 6 discrete afterglow re-pulses (sin^8 spikes) whose peaks decay across the tail, scaled by flicker.",
190
+ "amp": {
191
+ "envelope": [
192
+ {
193
+ "input": "life"
194
+ },
195
+ {
196
+ "param": "overshoot"
197
+ }
198
+ ]
199
+ },
200
+ "extras": {
201
+ "strike": {
202
+ "sub": [
203
+ 1,
204
+ {
205
+ "pow": [
206
+ {
207
+ "sub": [
208
+ 1,
209
+ {
210
+ "clamp01": {
211
+ "div": [
212
+ {
213
+ "input": "animMs"
214
+ },
215
+ 130
216
+ ]
217
+ }
218
+ }
219
+ ]
220
+ },
221
+ 5
222
+ ]
223
+ }
224
+ ]
225
+ },
226
+ "flash": {
227
+ "add": [
228
+ {
229
+ "exp": {
230
+ "div": [
231
+ {
232
+ "mul": [
233
+ -1,
234
+ {
235
+ "clamp01": {
236
+ "input": "life"
237
+ }
238
+ }
239
+ ]
240
+ },
241
+ 0.035
242
+ ]
243
+ }
244
+ },
245
+ {
246
+ "mul": [
247
+ {
248
+ "pow": [
249
+ {
250
+ "max": [
251
+ 0,
252
+ {
253
+ "sin": {
254
+ "mul": [
255
+ {
256
+ "clamp01": {
257
+ "input": "life"
258
+ }
259
+ },
260
+ 6,
261
+ 3.141592653589793,
262
+ 2
263
+ ]
264
+ }
265
+ }
266
+ ]
267
+ },
268
+ 8
269
+ ]
270
+ },
271
+ {
272
+ "mul": [
273
+ {
274
+ "pow": [
275
+ {
276
+ "sub": [
277
+ 1,
278
+ {
279
+ "clamp01": {
280
+ "input": "life"
281
+ }
282
+ }
283
+ ]
284
+ },
285
+ 2.2
286
+ ]
287
+ },
288
+ 0.28,
289
+ {
290
+ "param": "flicker"
291
+ }
292
+ ]
293
+ }
294
+ ]
295
+ }
296
+ ]
297
+ }
298
+ }
299
+ },
300
+ "reducedMotion": {
301
+ "peakMs": 130,
302
+ "holdMs": 300
303
+ }
304
+ },
305
+ "render": {
306
+ "params": {
307
+ "exposure": {
308
+ "type": "float",
309
+ "from": {
310
+ "lerp": [
311
+ "intensity",
312
+ 0.9,
313
+ 1.6
314
+ ]
315
+ }
316
+ },
317
+ "thickness": {
318
+ "type": "float",
319
+ "from": {
320
+ "mul": [
321
+ {
322
+ "baseline": "thickness"
323
+ },
324
+ {
325
+ "lerp": [
326
+ "intensity",
327
+ 0.7,
328
+ 1.35
329
+ ]
330
+ }
331
+ ]
332
+ }
333
+ },
334
+ "jagged": {
335
+ "type": "float",
336
+ "from": {
337
+ "mul": [
338
+ {
339
+ "baseline": "jagged"
340
+ },
341
+ {
342
+ "lerp": [
343
+ "intensity",
344
+ 0.8,
345
+ 1.2
346
+ ]
347
+ }
348
+ ]
349
+ }
350
+ },
351
+ "branches": {
352
+ "type": "int",
353
+ "clampMax": "MAX_FORKS",
354
+ "from": {
355
+ "round": {
356
+ "mul": [
357
+ {
358
+ "baseline": "branches"
359
+ },
360
+ {
361
+ "lerp": [
362
+ "intensity",
363
+ 0.5,
364
+ 1.25
365
+ ]
366
+ }
367
+ ]
368
+ }
369
+ }
370
+ },
371
+ "flashBright": {
372
+ "type": "float",
373
+ "from": {
374
+ "mul": [
375
+ {
376
+ "baseline": "flashBright"
377
+ },
378
+ {
379
+ "lerp": [
380
+ "intensity",
381
+ 0.6,
382
+ 1.4
383
+ ]
384
+ }
385
+ ]
386
+ }
387
+ },
388
+ "flicker": {
389
+ "type": "float",
390
+ "clamp01": true,
391
+ "from": {
392
+ "baseline": "flicker"
393
+ }
394
+ },
395
+ "overshoot": {
396
+ "type": "float",
397
+ "from": {
398
+ "mul": [
399
+ {
400
+ "baseline": "overshoot"
401
+ },
402
+ {
403
+ "lerp": [
404
+ "intensity",
405
+ 0.7,
406
+ 1.25
407
+ ]
408
+ }
409
+ ]
410
+ }
411
+ },
412
+ "style": {
413
+ "type": "float",
414
+ "from": {
415
+ "control": "whimsy"
416
+ }
417
+ }
418
+ },
419
+ "shadowHeightFrac": {
420
+ "add": [
421
+ {
422
+ "mul": [
423
+ {
424
+ "param": "thickness"
425
+ },
426
+ 14
427
+ ]
428
+ },
429
+ 0.4
430
+ ]
431
+ },
432
+ "consts": {
433
+ "MAX_FORKS": 7
434
+ },
435
+ "config": {
436
+ "usesOrigin": true
437
+ },
438
+ "backends": {
439
+ "webgl2": {
440
+ "stage": "fullscreen-triangle",
441
+ "blend": "screen",
442
+ "shader": {
443
+ "program": "lightning"
444
+ }
445
+ }
446
+ },
447
+ "fallbackOrder": [
448
+ "webgl2"
449
+ ]
450
+ },
451
+ "binding": {
452
+ "note": "CROSS-PLATFORM uniform-binding contract. Which render.params are NOT shader uniforms, the seed-keyed scatter field, the per-frame/host extras, the texture samplers, and the per-frame ARRAYS — one source of truth for the web u<Name> list, the Swift struct + packer, and the MSL struct. SHIPS in the portable .dope (the runtime derives its uniform bindings from it); the toolchain consumes it too for the Metal struct codegen. Only `x-build`, `slug` and `kind` stay toolchain-only. lightning's CPU-precomputed bolt polyline rides the frameArrays seam declared in `arrays` (uVerts/uBoltMeta), not the uniform struct.",
453
+ "excludeParams": [
454
+ "style",
455
+ "overshoot",
456
+ "flicker",
457
+ "durationMs",
458
+ "jagged",
459
+ "branches"
460
+ ],
461
+ "scatterKey": "boltSeed",
462
+ "scatterWeb": "uSeed",
463
+ "extras": [
464
+ {
465
+ "name": "strike",
466
+ "type": "float",
467
+ "web": "uStrike",
468
+ "note": "tempo.frame.extras.strike — the 130 ms ease-out-quint crack-in"
469
+ },
470
+ {
471
+ "name": "flash",
472
+ "type": "float",
473
+ "web": "uFlash",
474
+ "note": "tempo.frame.extras.flash — the strobe (primary flash + flicker afterglow)"
475
+ }
476
+ ],
477
+ "samplers": [],
478
+ "arrays": [
479
+ {
480
+ "name": "verts",
481
+ "web": "uVerts",
482
+ "size": 2,
483
+ "buffer": 1,
484
+ "note": "CPU-precomputed bolt polyline (x-build.logic): vertex i of bolt b at [b*VERTS_PER_BOLT + i], device px, gl y-up. Web/GL bind by NAME as the uniform vec2 array; Metal binds it as `constant float2 *uVerts` at fragment buffer 1."
485
+ },
486
+ {
487
+ "name": "meta",
488
+ "web": "uBoltMeta",
489
+ "size": 4,
490
+ "buffer": 2,
491
+ "note": "Per-bolt (segCount, radFrac, fadeMul, isMain); `constant float4 *uBoltMeta` at fragment buffer 2 on Metal."
492
+ }
493
+ ]
494
+ }
495
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@dopaminefx/effect-lightning",
3
+ "version": "0.1.0",
4
+ "description": "Lightning — a high-energy lightning-strike power-up effect for Dopamine.",
5
+ "keywords": [
6
+ "dopamine-effect"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src"
21
+ ],
22
+ "sideEffects": [
23
+ "./src/index.ts",
24
+ "./dist/index.js"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.json"
28
+ },
29
+ "dependencies": {
30
+ "@dopaminefx/core": "^0.1.0"
31
+ },
32
+ "license": "MIT",
33
+ "author": "10in30",
34
+ "homepage": "https://github.com/10in30/dopamine#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/10in30/dopamine.git",
38
+ "directory": "effects/lightning/web"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/10in30/dopamine/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Lightning — Dopamine's high-energy "power-up / boost" STRIKE, as an
3
+ * `EffectFactory`. A jagged, fbm-perturbed branching electric arc cracks into the
4
+ * action point (uOrigin) with a hard white STROBE FLASH, a few secondary forks,
5
+ * and a brief flicker afterglow that decays. Electric OKLCH blues/violets to a
6
+ * hot white core. Casts a hard, sharp shadow (the shadow pass).
7
+ *
8
+ * FULLY DATA-DRIVEN: everything that isn't the GLSL or the bolt precompute
9
+ * lives in lightning.dope.json — the mood→params mapping + palette (the
10
+ * loader), the per-frame logic (`tempo.frame`: the impact-envelope amp, the
11
+ * 130 ms strike crack-in and the flash/strobe — what lightning-tempo.ts used to
12
+ * be), `render.shadowHeightFrac`, `render.consts` (MAX_FORKS),
13
+ * `render.config`, `tempo.reducedMotion` and the uniform `binding` contract
14
+ * (whose `arrays` section declares the uVerts/uBoltMeta frame arrays).
15
+ * `registerDopeEffect` interprets that data through the generic pass runner.
16
+ *
17
+ * The one genuinely code-shaped piece is the CPU bolt precompute — the
18
+ * fragment-independent polyline computed once per frame (lightning-logic.ts,
19
+ * the single source the Swift/Kotlin renderers are TRANSPILED from) — which
20
+ * rides the `hooks.frameArrays` seam here, exactly as the generated native
21
+ * factories wire the generated renderer into their runners' frameArrays seams.
22
+ *
23
+ * mood = register: serene = a soft single cool arc; celebratory = a lively
24
+ * branched bolt; electric = a violent multi-fork strike + bright strobe.
25
+ * intensity = bolt thickness + branch count + flash brightness.
26
+ * whimsy (= style) = photoreal plasma glow (0) to flat cel comic bolt + harder
27
+ * animate-on-twos strobe (1).
28
+ */
29
+
30
+ import { LIGHTNING_FRAGMENT_SRC, LIGHTNING_VERTEX_SRC } from "./lightning-shader.js";
31
+ import { computeLightningArrays } from "./lightning-logic.js";
32
+ import {
33
+ parseDope,
34
+ registerDopeEffect,
35
+ type EffectFactory,
36
+ type PassParams,
37
+ } from "@dopaminefx/core";
38
+ import doc from "./lightning.dope.json";
39
+
40
+ const DOPE = parseDope(doc as object);
41
+
42
+ export interface LightningParams extends PassParams {
43
+ exposure: number;
44
+ thickness: number;
45
+ jagged: number;
46
+ branches: number;
47
+ flashBright: number;
48
+ flicker: number;
49
+ overshoot: number;
50
+ boltSeed: number;
51
+ style: number; // = whimsy (drives the on-twos cel jitter)
52
+ }
53
+
54
+ // The whole factory (resolve / uniforms / bindings / frame / shadow / reduced
55
+ // motion / program registration) is data: lightning.dope.json interpreted by
56
+ // the core backbone. The jagged bolt polyline (the part that used to cost
57
+ // ~220 fbm/pixel) is PRECOMPUTED on the CPU once per frame and fed to the
58
+ // shader through the binding.arrays contract (uVerts/uBoltMeta) — the
59
+ // `frameArrays` hook below is the code-shaped precompute call.
60
+ export const lightning = registerDopeEffect(
61
+ DOPE,
62
+ { vertex: LIGHTNING_VERTEX_SRC, fragment: LIGHTNING_FRAGMENT_SRC },
63
+ {
64
+ hooks: {
65
+ frameArrays: ({ animMs, life }, params, geom) => {
66
+ const p = params as LightningParams;
67
+ const { verts, meta } = computeLightningArrays(
68
+ p.style, p.thickness, p.jagged, p.branches, p.boltSeed,
69
+ geom.width, geom.height, geom.origin.x, geom.origin.y, animMs, life,
70
+ );
71
+ return [
72
+ { name: "uVerts", size: 2, data: verts },
73
+ { name: "uBoltMeta", size: 4, data: meta },
74
+ ];
75
+ },
76
+ },
77
+ },
78
+ ) as EffectFactory<PassParams> as EffectFactory<LightningParams>;
79
+
80
+ export default lightning;