Skip to main content

proof_engine/render/shader_graph/
presets.rs

1//! Pre-built shader graph presets for Proof Engine visual effects.
2//!
3//! Each preset is a fully-wired ShaderGraph that produces a distinct visual style.
4//! Presets are the primary way game code applies visual effects to the scene.
5
6use super::{ShaderGraph, ShaderParameter, ParameterValue};
7use super::nodes::NodeType;
8use crate::math::MathFunction;
9
10pub struct ShaderPreset;
11
12impl ShaderPreset {
13    // ── Boss / Floor Effect Presets ────────────────────────────────────────────
14
15    /// Void Protocol — swirling void with Lorenz attractor chaos lines.
16    /// Used for the Void Architect boss encounter.
17    pub fn void_protocol() -> ShaderGraph {
18        let mut g = ShaderGraph::new("void_protocol");
19
20        // Inputs
21        let uv   = g.add_node_at(NodeType::UvCoord, 0.0, 0.0);
22        let time = g.add_node_at(NodeType::Time,    0.0, 80.0);
23
24        // Time-scaled UV
25        let time_scale = g.add_node_at(NodeType::ConstFloat(0.3), 200.0, 80.0);
26        let t_scaled   = g.add_node_at(NodeType::Multiply, 400.0, 80.0);
27        let _ = g.connect(time, 0, t_scaled, 0);
28        let _ = g.connect(time_scale, 0, t_scaled, 1);
29
30        // Combine UV with time offset for animated noise
31        let uv_offset = g.add_node_at(NodeType::CombineVec2, 300.0, 0.0);
32        let sin_t     = g.add_node_at(NodeType::Sin, 500.0, 40.0);
33        let _ = g.connect(t_scaled, 0, sin_t, 0);
34        // (just UV for now — sin added to create swirl)
35
36        // Lorenz attractor field
37        let lorenz = g.add_node_at(NodeType::LorenzAttractor, 600.0, 0.0);
38        let _ = g.connect(uv,      0, lorenz, 0);
39        let _ = g.connect(t_scaled,0, lorenz, 1);
40
41        // Mandelbrot overlay
42        let mandel = g.add_node_at(NodeType::Mandelbrot, 600.0, 150.0);
43        let zoom   = g.add_node_at(NodeType::Uniform("u_void_zoom".to_string(), super::nodes::SocketType::Float), 400.0, 200.0);
44        let _ = g.connect(uv,   0, mandel, 0);
45        let _ = g.connect(zoom, 0, mandel, 2);
46
47        // Mix lorenz and mandelbrot
48        let mix_lm   = g.add_node_at(NodeType::Mix, 800.0, 75.0);
49        let mix_t    = g.add_node_at(NodeType::ConstFloat(0.4), 600.0, 300.0);
50        let _ = g.connect(lorenz, 0, mix_lm, 0);
51        let _ = g.connect(mandel, 0, mix_lm, 1);
52        let _ = g.connect(mix_t,  0, mix_lm, 2);
53
54        // Color: deep purple → cyan based on value
55        let hsv_h   = g.add_node_at(NodeType::Remap, 1000.0, 0.0);
56        let h_min   = g.add_node_at(NodeType::ConstFloat(0.65), 800.0, -80.0);
57        let h_max   = g.add_node_at(NodeType::ConstFloat(0.85), 800.0, -160.0);
58        let _ = g.connect(mix_lm, 0, hsv_h, 0);
59        let _ = g.connect(h_min,  0, hsv_h, 3);
60        let _ = g.connect(h_max,  0, hsv_h, 4);
61
62        let sat     = g.add_node_at(NodeType::ConstFloat(0.9), 1000.0, 80.0);
63        let val     = g.add_node_at(NodeType::ConstFloat(0.8), 1000.0, 160.0);
64        let hsv_rgb = g.add_node_at(NodeType::CombineVec3, 1200.0, 80.0);
65        let _ = g.connect(hsv_h, 0, hsv_rgb, 0);
66        let _ = g.connect(sat,   0, hsv_rgb, 1);
67        let _ = g.connect(val,   0, hsv_rgb, 2);
68        let to_rgb  = g.add_node_at(NodeType::HsvToRgb, 1400.0, 80.0);
69        let _ = g.connect(hsv_rgb, 0, to_rgb, 0);
70
71        // Vignette
72        let vig     = g.add_node_at(NodeType::Vignette, 1200.0, 250.0);
73        let vig_str = g.add_node_at(NodeType::ConstFloat(0.6), 1000.0, 320.0);
74        let _ = g.connect(uv,      0, vig, 0);
75        let _ = g.connect(vig_str, 0, vig, 1);
76
77        // Apply vignette to color
78        let mul_vig = g.add_node_at(NodeType::Multiply, 1600.0, 80.0);
79        let _ = g.connect(to_rgb, 0, mul_vig, 0);
80        let _ = g.connect(vig,    0, mul_vig, 1);
81
82        // Alpha = 1
83        let alpha   = g.add_node_at(NodeType::ConstFloat(1.0), 1600.0, 250.0);
84        let combine = g.add_node_at(NodeType::CombineVec4, 1800.0, 150.0);
85        let _ = g.connect(mul_vig, 0, combine, 0);
86        let _ = g.connect(alpha,   0, combine, 1);
87
88        let out = g.add_node_at(NodeType::OutputColor, 2000.0, 150.0);
89        g.set_output(out);
90        let _ = g.connect(combine, 0, out, 0);
91
92        g.add_parameter(ShaderParameter {
93            name:      "void_zoom".to_string(),
94            glsl_name: "u_void_zoom".to_string(),
95            value:     ParameterValue::Float(1.0),
96            driver:    Some(MathFunction::Sine { amplitude: 0.3, frequency: 0.5, phase: 0.0 }),
97            min:       0.1,
98            max:       3.0,
99        });
100
101        g
102    }
103
104    /// Blood Pact — crimson cascading blood with fractal veins.
105    pub fn blood_pact() -> ShaderGraph {
106        let mut g = ShaderGraph::new("blood_pact");
107        let uv   = g.add_node(NodeType::UvCoord);
108        let time = g.add_node(NodeType::Time);
109
110        // FBM for vein texture
111        let fbm  = g.add_node(NodeType::Fbm);
112        let oct  = g.add_node(NodeType::ConstFloat(5.0));
113        let lac  = g.add_node(NodeType::ConstFloat(2.0));
114        let gain = g.add_node(NodeType::ConstFloat(0.5));
115        let _ = g.connect(uv,   0, fbm, 0);
116        let _ = g.connect(oct,  0, fbm, 1);
117        let _ = g.connect(lac,  0, fbm, 2);
118        let _ = g.connect(gain, 0, fbm, 3);
119
120        // Time-animated offset
121        let t_slow = g.add_node(NodeType::Multiply);
122        let t_sc   = g.add_node(NodeType::ConstFloat(0.15));
123        let _ = g.connect(time,  0, t_slow, 0);
124        let _ = g.connect(t_sc,  0, t_slow, 1);
125
126        // FBM value → blood color ramp
127        // Dark red to bright crimson via hue ramp
128        let hue_base = g.add_node(NodeType::ConstFloat(0.0));  // red
129        let hue      = g.add_node(NodeType::Add);
130        let hue_var  = g.add_node(NodeType::ConstFloat(0.03));
131        let _ = g.connect(hue_base, 0, hue, 0);
132        let _ = g.connect(hue_var,  0, hue, 1);
133
134        let sat_v = g.add_node(NodeType::ConstFloat(0.95));
135        let val_v = g.add_node(NodeType::Multiply);
136        let bright= g.add_node(NodeType::ConstFloat(0.8));
137        let _ = g.connect(fbm,   0, val_v, 0);
138        let _ = g.connect(bright,0, val_v, 1);
139
140        let hsv   = g.add_node(NodeType::CombineVec3);
141        let _ = g.connect(hue,   0, hsv, 0);
142        let _ = g.connect(sat_v, 0, hsv, 1);
143        let _ = g.connect(val_v, 0, hsv, 2);
144
145        let rgb   = g.add_node(NodeType::HsvToRgb);
146        let _ = g.connect(hsv, 0, rgb, 0);
147
148        // Film grain
149        let grain     = g.add_node(NodeType::FilmGrain);
150        let grain_str = g.add_node(NodeType::ConstFloat(0.03));
151        let _ = g.connect(uv,        0, grain, 0);
152        let _ = g.connect(time,      0, grain, 1);
153        let _ = g.connect(grain_str, 0, grain, 2);
154
155        let rgb_grain = g.add_node(NodeType::CombineVec3);
156        let grain_v3  = g.add_node(NodeType::CombineVec3);
157        // Just add grain to final
158        let add_grain = g.add_node(NodeType::Add);
159        let _ = g.connect(rgb,   0, add_grain, 0);
160        let _ = g.connect(grain, 0, add_grain, 1);
161
162        let alpha = g.add_node(NodeType::ConstFloat(1.0));
163        let out4  = g.add_node(NodeType::CombineVec4);
164        let _ = g.connect(add_grain, 0, out4, 0);
165        let _ = g.connect(alpha,     0, out4, 1);
166
167        let out = g.add_node(NodeType::OutputColor);
168        g.set_output(out);
169        let _ = g.connect(out4, 0, out, 0);
170
171        let _ = (rgb_grain, grain_v3, t_slow); // suppress unused warnings
172        g
173    }
174
175    /// Emerald Engine — mechanical emerald fractals and circuitry.
176    pub fn emerald_engine() -> ShaderGraph {
177        let mut g = ShaderGraph::new("emerald_engine");
178        let uv   = g.add_node(NodeType::UvCoord);
179        let time = g.add_node(NodeType::Time);
180
181        // Grid + voronoi for circuit pattern
182        let grid_scale = g.add_node(NodeType::ConstFloat(15.0));
183        let grid       = g.add_node(NodeType::Grid);
184        let _ = g.connect(uv,         0, grid, 0);
185        let _ = g.connect(grid_scale, 0, grid, 1);
186
187        let vor_scale = g.add_node(NodeType::ConstFloat(8.0));
188        let voronoi   = g.add_node(NodeType::Voronoi);
189        let vor_jit   = g.add_node(NodeType::ConstFloat(0.7));
190        let _ = g.connect(uv,        0, voronoi, 0);
191        let _ = g.connect(vor_scale, 0, voronoi, 1);
192        let _ = g.connect(vor_jit,   0, voronoi, 2);
193
194        // Combine grid and voronoi
195        let grid_mix  = g.add_node(NodeType::Add);
196        let mix_w     = g.add_node(NodeType::ConstFloat(0.5));
197        let _ = g.connect(grid,    0, grid_mix, 0);
198        let _ = g.connect(voronoi, 0, grid_mix, 1);
199
200        // Emerald color: deep green → bright emerald
201        let em_low  = g.add_node(NodeType::ConstVec3(0.0, 0.2, 0.05));
202        let em_high = g.add_node(NodeType::ConstVec3(0.1, 0.9, 0.3));
203        let em_mix  = g.add_node(NodeType::Mix);
204        let _ = g.connect(em_low,  0, em_mix, 0);
205        let _ = g.connect(em_high, 0, em_mix, 1);
206        let _ = g.connect(grid_mix,0, em_mix, 2);
207
208        // Animated pulse via time
209        let t_fast  = g.add_node(NodeType::Multiply);
210        let t_scale = g.add_node(NodeType::ConstFloat(2.0));
211        let _ = g.connect(time,    0, t_fast, 0);
212        let _ = g.connect(t_scale, 0, t_fast, 1);
213        let pulse   = g.add_node(NodeType::Sin);
214        let _ = g.connect(t_fast, 0, pulse, 0);
215        let p_half  = g.add_node(NodeType::Multiply);
216        let ph_c    = g.add_node(NodeType::ConstFloat(0.1));
217        let _ = g.connect(pulse, 0, p_half, 0);
218        let _ = g.connect(ph_c,  0, p_half, 1);
219
220        // Brighten with pulse
221        let bright  = g.add_node(NodeType::Add);
222        let _ = g.connect(em_mix, 0, bright, 0);
223        let _ = g.connect(p_half, 0, bright, 1);
224
225        let alpha = g.add_node(NodeType::ConstFloat(1.0));
226        let out4  = g.add_node(NodeType::CombineVec4);
227        let _ = g.connect(bright, 0, out4, 0);
228        let _ = g.connect(alpha,  0, out4, 1);
229
230        let out = g.add_node(NodeType::OutputColor);
231        g.set_output(out);
232        let _ = g.connect(out4, 0, out, 0);
233
234        let _ = mix_w;
235        g
236    }
237
238    /// Corruption High — purple-black corruption spreading fractal noise.
239    pub fn corruption_high() -> ShaderGraph {
240        let mut g = ShaderGraph::new("corruption_high");
241        let uv   = g.add_node(NodeType::UvCoord);
242        let time = g.add_node(NodeType::Time);
243
244        // Animated Julia set for corruption tendrils
245        let julia  = g.add_node(NodeType::Julia);
246        let maxitr = g.add_node(NodeType::ConstFloat(120.0));
247        let zoom   = g.add_node(NodeType::ConstFloat(1.5));
248        let cx     = g.add_node(NodeType::Uniform("u_corrupt_cx".to_string(), super::nodes::SocketType::Float));
249        let cy     = g.add_node(NodeType::Uniform("u_corrupt_cy".to_string(), super::nodes::SocketType::Float));
250        let _ = g.connect(uv,     0, julia, 0);
251        let _ = g.connect(maxitr, 0, julia, 1);
252        let _ = g.connect(zoom,   0, julia, 2);
253        let _ = g.connect(cx,     0, julia, 3);
254        let _ = g.connect(cy,     0, julia, 4);
255
256        // Map to corrupt purple palette
257        let hue_base = g.add_node(NodeType::ConstFloat(0.75)); // purple
258        let hue      = g.add_node(NodeType::Add);
259        let hue_var  = g.add_node(NodeType::Multiply);
260        let julia_scaled = g.add_node(NodeType::Multiply);
261        let j_scale = g.add_node(NodeType::ConstFloat(0.1));
262        let _ = g.connect(julia,  0, julia_scaled, 0);
263        let _ = g.connect(j_scale,0, julia_scaled, 1);
264        let _ = g.connect(hue_base, 0, hue, 0);
265        let _ = g.connect(julia_scaled, 0, hue, 1);
266        let _ = (hue_var,);
267
268        let sat = g.add_node(NodeType::ConstFloat(0.85));
269        let val = g.add_node(NodeType::Clamp);
270        let jv  = g.add_node(NodeType::Multiply);
271        let jvc = g.add_node(NodeType::ConstFloat(0.9));
272        let _ = g.connect(julia, 0, jv, 0);
273        let _ = g.connect(jvc,   0, jv, 1);
274        let vmin = g.add_node(NodeType::ConstFloat(0.0));
275        let vmax = g.add_node(NodeType::ConstFloat(1.0));
276        let _ = g.connect(jv,   0, val, 0);
277        let _ = g.connect(vmin, 0, val, 1);
278        let _ = g.connect(vmax, 0, val, 2);
279
280        let hsv = g.add_node(NodeType::CombineVec3);
281        let _ = g.connect(hue, 0, hsv, 0);
282        let _ = g.connect(sat, 0, hsv, 1);
283        let _ = g.connect(val, 0, hsv, 2);
284        let rgb = g.add_node(NodeType::HsvToRgb);
285        let _ = g.connect(hsv, 0, rgb, 0);
286
287        // Glitch offset
288        let glitch  = g.add_node(NodeType::GlitchOffset);
289        let gstr    = g.add_node(NodeType::ConstFloat(0.4));
290        let _ = g.connect(uv,    0, glitch, 0);
291        let _ = g.connect(time,  0, glitch, 1);
292        let _ = g.connect(gstr,  0, glitch, 2);
293
294        let alpha = g.add_node(NodeType::ConstFloat(1.0));
295        let out4  = g.add_node(NodeType::CombineVec4);
296        let _ = g.connect(rgb,  0, out4, 0);
297        let _ = g.connect(alpha,0, out4, 1);
298
299        let out = g.add_node(NodeType::OutputColor);
300        g.set_output(out);
301        let _ = g.connect(out4, 0, out, 0);
302
303        g.add_parameter(ShaderParameter {
304            name:      "corrupt_cx".to_string(),
305            glsl_name: "u_corrupt_cx".to_string(),
306            value:     ParameterValue::Float(-0.7),
307            driver:    Some(MathFunction::Sine { amplitude: 0.4, frequency: 0.2, phase: 0.0 }),
308            min:       -2.0,
309            max:       2.0,
310        });
311        g.add_parameter(ShaderParameter {
312            name:      "corrupt_cy".to_string(),
313            glsl_name: "u_corrupt_cy".to_string(),
314            value:     ParameterValue::Float(0.27),
315            driver:    Some(MathFunction::Cosine { amplitude: 0.3, frequency: 0.17, phase: 1.57 }),
316            min:       -2.0,
317            max:       2.0,
318        });
319        g
320    }
321
322    /// Null Fight — black void with sharp white math geometry.
323    pub fn null_fight() -> ShaderGraph {
324        let mut g = ShaderGraph::new("null_fight");
325        let uv   = g.add_node(NodeType::UvCoord);
326        let time = g.add_node(NodeType::Time);
327
328        // SDF star burst
329        let star  = g.add_node(NodeType::StarBurst);
330        let arms  = g.add_node(NodeType::ConstFloat(12.0));
331        let sharp = g.add_node(NodeType::ConstFloat(0.8));
332        let _ = g.connect(uv,   0, star, 0);
333        let _ = g.connect(arms, 0, star, 1);
334        let _ = g.connect(sharp,0, star, 2);
335
336        // Rings overlay
337        let rings = g.add_node(NodeType::Rings);
338        let rcount= g.add_node(NodeType::ConstFloat(8.0));
339        let rwidth= g.add_node(NodeType::ConstFloat(0.3));
340        let _ = g.connect(uv,    0, rings, 0);
341        let _ = g.connect(rcount,0, rings, 1);
342        let _ = g.connect(rwidth,0, rings, 2);
343
344        // Combine
345        let combined = g.add_node(NodeType::Max);
346        let _ = g.connect(star,  0, combined, 0);
347        let _ = g.connect(rings, 0, combined, 1);
348
349        // Time-pulsed brightness
350        let t_pulse = g.add_node(NodeType::Sin);
351        let _ = g.connect(time, 0, t_pulse, 0);
352        let t_remap = g.add_node(NodeType::Remap);
353        let rm1 = g.add_node(NodeType::ConstFloat(-1.0));
354        let rm2 = g.add_node(NodeType::ConstFloat(1.0));
355        let rm3 = g.add_node(NodeType::ConstFloat(0.7));
356        let rm4 = g.add_node(NodeType::ConstFloat(1.0));
357        let _ = g.connect(t_pulse, 0, t_remap, 0);
358        let _ = g.connect(rm1, 0, t_remap, 1);
359        let _ = g.connect(rm2, 0, t_remap, 2);
360        let _ = g.connect(rm3, 0, t_remap, 3);
361        let _ = g.connect(rm4, 0, t_remap, 4);
362
363        let brightness = g.add_node(NodeType::Multiply);
364        let _ = g.connect(combined, 0, brightness, 0);
365        let _ = g.connect(t_remap,  0, brightness, 1);
366
367        // Black and white
368        let white = g.add_node(NodeType::ConstVec3(1.0, 1.0, 1.0));
369        let color = g.add_node(NodeType::Multiply);
370        let _ = g.connect(white,     0, color, 0);
371        let _ = g.connect(brightness,0, color, 1);
372
373        let alpha = g.add_node(NodeType::ConstFloat(1.0));
374        let out4  = g.add_node(NodeType::CombineVec4);
375        let _ = g.connect(color, 0, out4, 0);
376        let _ = g.connect(alpha, 0, out4, 1);
377
378        let out = g.add_node(NodeType::OutputColor);
379        g.set_output(out);
380        let _ = g.connect(out4, 0, out, 0);
381        g
382    }
383
384    /// Paradox Invert — inverted reality with chromatic splitting and recursion.
385    pub fn paradox_invert() -> ShaderGraph {
386        let mut g = ShaderGraph::new("paradox_invert");
387        let uv   = g.add_node(NodeType::UvCoord);
388        let time = g.add_node(NodeType::Time);
389
390        // Chromatic aberration
391        let chroma = g.add_node(NodeType::ChromaticAberration);
392        let c_str  = g.add_node(NodeType::ConstFloat(0.015));
393        let _ = g.connect(uv,    0, chroma, 0);
394        let _ = g.connect(c_str, 0, chroma, 1);
395
396        // Barrel distort
397        let barrel = g.add_node(NodeType::BarrelDistort);
398        let b_str  = g.add_node(NodeType::ConstFloat(-0.3));
399        let _ = g.connect(chroma, 0, barrel, 0);
400        let _ = g.connect(b_str,  0, barrel, 1);
401
402        // FBM base
403        let fbm  = g.add_node(NodeType::Fbm);
404        let oct  = g.add_node(NodeType::ConstFloat(3.0));
405        let lac  = g.add_node(NodeType::ConstFloat(2.0));
406        let gain = g.add_node(NodeType::ConstFloat(0.6));
407        let _ = g.connect(barrel, 0, fbm, 0);
408        let _ = g.connect(oct,    0, fbm, 1);
409        let _ = g.connect(lac,    0, fbm, 2);
410        let _ = g.connect(gain,   0, fbm, 3);
411
412        // Invert
413        let inv = g.add_node(NodeType::OneMinus);
414        let _ = g.connect(fbm, 0, inv, 0);
415
416        // Color: inverted rainbow via hue rotation driven by time
417        let hue_rot = g.add_node(NodeType::HueRotate);
418        let base_col= g.add_node(NodeType::ConstVec3(0.5, 0.2, 0.8));
419        let time_deg= g.add_node(NodeType::Multiply);
420        let deg_c   = g.add_node(NodeType::ConstFloat(90.0));
421        let _ = g.connect(time,     0, time_deg, 0);
422        let _ = g.connect(deg_c,    0, time_deg, 1);
423        let _ = g.connect(base_col, 0, hue_rot,  0);
424        let _ = g.connect(time_deg, 0, hue_rot,  1);
425
426        // Multiply inverted value by hue-rotated color
427        let final_col = g.add_node(NodeType::Multiply);
428        let _ = g.connect(hue_rot, 0, final_col, 0);
429        let _ = g.connect(inv,     0, final_col, 1);
430
431        let alpha = g.add_node(NodeType::ConstFloat(1.0));
432        let out4  = g.add_node(NodeType::CombineVec4);
433        let _ = g.connect(final_col, 0, out4, 0);
434        let _ = g.connect(alpha,     0, out4, 1);
435
436        let out = g.add_node(NodeType::OutputColor);
437        g.set_output(out);
438        let _ = g.connect(out4, 0, out, 0);
439        g
440    }
441
442    /// Fire Elemental — flickering fire with heat haze and orange-red palette.
443    pub fn fire_elemental() -> ShaderGraph {
444        let mut g = ShaderGraph::new("fire_elemental");
445        let uv   = g.add_node(NodeType::UvCoord);
446        let time = g.add_node(NodeType::Time);
447
448        // Heat haze on UV
449        let haze  = g.add_node(NodeType::HeatHaze);
450        let hz_str= g.add_node(NodeType::ConstFloat(0.03));
451        let hz_spd= g.add_node(NodeType::ConstFloat(2.0));
452        let _ = g.connect(uv,    0, haze, 0);
453        let _ = g.connect(time,  0, haze, 1);
454        let _ = g.connect(hz_str,0, haze, 2);
455        let _ = g.connect(hz_spd,0, haze, 3);
456
457        // FBM for fire shape
458        let fbm  = g.add_node(NodeType::Fbm);
459        let oct  = g.add_node(NodeType::ConstFloat(4.0));
460        let lac  = g.add_node(NodeType::ConstFloat(2.2));
461        let gn   = g.add_node(NodeType::ConstFloat(0.5));
462        let _ = g.connect(haze, 0, fbm, 0);
463        let _ = g.connect(oct,  0, fbm, 1);
464        let _ = g.connect(lac,  0, fbm, 2);
465        let _ = g.connect(gn,   0, fbm, 3);
466
467        // Map FBM to fire gradient: black → red → orange → yellow → white
468        let fire_low  = g.add_node(NodeType::ConstVec3(0.8, 0.1, 0.0));
469        let fire_high = g.add_node(NodeType::ConstVec3(1.0, 0.9, 0.1));
470        let fire_mix  = g.add_node(NodeType::Mix);
471        let _ = g.connect(fire_low,  0, fire_mix, 0);
472        let _ = g.connect(fire_high, 0, fire_mix, 1);
473        let _ = g.connect(fbm,       0, fire_mix, 2);
474
475        // Emissive glow
476        let glow   = g.add_node(NodeType::Multiply);
477        let glow_c = g.add_node(NodeType::ConstFloat(1.8));
478        let _ = g.connect(fire_mix, 0, glow, 0);
479        let _ = g.connect(glow_c,   0, glow, 1);
480
481        let alpha = g.add_node(NodeType::ConstFloat(1.0));
482        let out4  = g.add_node(NodeType::CombineVec4);
483        let _ = g.connect(glow,  0, out4, 0);
484        let _ = g.connect(alpha, 0, out4, 1);
485
486        let out = g.add_node(NodeType::OutputColor);
487        g.set_output(out);
488        let _ = g.connect(out4, 0, out, 0);
489        g
490    }
491
492    /// Ice Elemental — crystalline ice with refraction and cold blue palette.
493    pub fn ice_elemental() -> ShaderGraph {
494        let mut g = ShaderGraph::new("ice_elemental");
495        let uv   = g.add_node(NodeType::UvCoord);
496        let time = g.add_node(NodeType::Time);
497
498        // Voronoi for crystal facets
499        let vor   = g.add_node(NodeType::Voronoi);
500        let v_sc  = g.add_node(NodeType::ConstFloat(12.0));
501        let v_jit = g.add_node(NodeType::ConstFloat(0.5));
502        let _ = g.connect(uv,   0, vor, 0);
503        let _ = g.connect(v_sc, 0, vor, 1);
504        let _ = g.connect(v_jit,0, vor, 2);
505
506        // Rings overlay
507        let rings  = g.add_node(NodeType::Rings);
508        let r_cnt  = g.add_node(NodeType::ConstFloat(6.0));
509        let r_wid  = g.add_node(NodeType::ConstFloat(0.4));
510        let _ = g.connect(uv,   0, rings, 0);
511        let _ = g.connect(r_cnt,0, rings, 1);
512        let _ = g.connect(r_wid,0, rings, 2);
513
514        // Mix voronoi and rings
515        let combined = g.add_node(NodeType::Add);
516        let _ = g.connect(vor,  0, combined, 0);
517        let _ = g.connect(rings,0, combined, 1);
518
519        // Time-animated shimmer
520        let shimmer = g.add_node(NodeType::Sin);
521        let t_fast  = g.add_node(NodeType::Multiply);
522        let t_sc    = g.add_node(NodeType::ConstFloat(3.0));
523        let _ = g.connect(time, 0, t_fast, 0);
524        let _ = g.connect(t_sc, 0, t_fast, 1);
525        let _ = g.connect(t_fast, 0, shimmer, 0);
526
527        let shim_m  = g.add_node(NodeType::Multiply);
528        let shim_c  = g.add_node(NodeType::ConstFloat(0.1));
529        let _ = g.connect(shimmer, 0, shim_m, 0);
530        let _ = g.connect(shim_c,  0, shim_m, 1);
531        let with_shim = g.add_node(NodeType::Add);
532        let _ = g.connect(combined, 0, with_shim, 0);
533        let _ = g.connect(shim_m,   0, with_shim, 1);
534
535        // Ice color: deep blue → white
536        let ice_dark  = g.add_node(NodeType::ConstVec3(0.05, 0.2, 0.5));
537        let ice_light = g.add_node(NodeType::ConstVec3(0.8, 0.95, 1.0));
538        let ice_mix   = g.add_node(NodeType::Mix);
539        let _ = g.connect(ice_dark,  0, ice_mix, 0);
540        let _ = g.connect(ice_light, 0, ice_mix, 1);
541        let _ = g.connect(with_shim, 0, ice_mix, 2);
542
543        let alpha = g.add_node(NodeType::ConstFloat(1.0));
544        let out4  = g.add_node(NodeType::CombineVec4);
545        let _ = g.connect(ice_mix, 0, out4, 0);
546        let _ = g.connect(alpha,   0, out4, 1);
547
548        let out = g.add_node(NodeType::OutputColor);
549        g.set_output(out);
550        let _ = g.connect(out4, 0, out, 0);
551        g
552    }
553
554    /// Aurora — shifting aurora borealis bands.
555    pub fn aurora() -> ShaderGraph {
556        let mut g = ShaderGraph::new("aurora");
557        let uv   = g.add_node(NodeType::UvCoord);
558        let time = g.add_node(NodeType::Time);
559
560        // Slow wave motion
561        let t_slow = g.add_node(NodeType::Multiply);
562        let ts_c   = g.add_node(NodeType::ConstFloat(0.2));
563        let _ = g.connect(time,  0, t_slow, 0);
564        let _ = g.connect(ts_c,  0, t_slow, 1);
565
566        // FBM for aurora shape
567        let fbm  = g.add_node(NodeType::Fbm);
568        let oct  = g.add_node(NodeType::ConstFloat(4.0));
569        let lac  = g.add_node(NodeType::ConstFloat(2.0));
570        let gn   = g.add_node(NodeType::ConstFloat(0.5));
571        let _ = g.connect(uv,    0, fbm, 0);
572        let _ = g.connect(oct,   0, fbm, 1);
573        let _ = g.connect(lac,   0, fbm, 2);
574        let _ = g.connect(gn,    0, fbm, 3);
575
576        // Sine bands driven by FBM
577        let band_sin = g.add_node(NodeType::Sin);
578        let band_mul = g.add_node(NodeType::Multiply);
579        let band_c   = g.add_node(NodeType::ConstFloat(8.0));
580        let _ = g.connect(fbm,    0, band_mul, 0);
581        let _ = g.connect(band_c, 0, band_mul, 1);
582        let _ = g.connect(band_mul, 0, band_sin, 0);
583
584        // Map to aurora green-cyan-purple palette
585        let hue    = g.add_node(NodeType::Remap);
586        let h_min  = g.add_node(NodeType::ConstFloat(0.3));
587        let h_max  = g.add_node(NodeType::ConstFloat(0.8));
588        let _ = g.connect(band_sin, 0, hue, 0);
589        let _ = g.connect(h_min,    0, hue, 3);
590        let _ = g.connect(h_max,    0, hue, 4);
591
592        let sat   = g.add_node(NodeType::ConstFloat(0.7));
593        let val   = g.add_node(NodeType::ConstFloat(0.9));
594        let hsv   = g.add_node(NodeType::CombineVec3);
595        let _ = g.connect(hue, 0, hsv, 0);
596        let _ = g.connect(sat, 0, hsv, 1);
597        let _ = g.connect(val, 0, hsv, 2);
598        let rgb   = g.add_node(NodeType::HsvToRgb);
599        let _ = g.connect(hsv, 0, rgb, 0);
600
601        let alpha = g.add_node(NodeType::ConstFloat(1.0));
602        let out4  = g.add_node(NodeType::CombineVec4);
603        let _ = g.connect(rgb,   0, out4, 0);
604        let _ = g.connect(alpha, 0, out4, 1);
605
606        let out = g.add_node(NodeType::OutputColor);
607        g.set_output(out);
608        let _ = g.connect(out4, 0, out, 0);
609
610        let _ = t_slow;
611        g
612    }
613
614    /// Static — TV static noise effect.
615    pub fn static_noise() -> ShaderGraph {
616        let mut g = ShaderGraph::new("static_noise");
617        let uv   = g.add_node(NodeType::UvCoord);
618        let time = g.add_node(NodeType::Time);
619
620        let grain     = g.add_node(NodeType::FilmGrain);
621        let grain_str = g.add_node(NodeType::ConstFloat(2.0));
622        let _ = g.connect(uv,        0, grain, 0);
623        let _ = g.connect(time,      0, grain, 1);
624        let _ = g.connect(grain_str, 0, grain, 2);
625
626        let scanlines = g.add_node(NodeType::Scanlines);
627        let scan_int  = g.add_node(NodeType::ConstFloat(0.4));
628        let scan_cnt  = g.add_node(NodeType::ConstFloat(400.0));
629        let _ = g.connect(uv,       0, scanlines, 0);
630        let _ = g.connect(scan_int, 0, scanlines, 1);
631        let _ = g.connect(scan_cnt, 0, scanlines, 2);
632
633        let combined = g.add_node(NodeType::Multiply);
634        let _ = g.connect(grain,    0, combined, 0);
635        let _ = g.connect(scanlines,0, combined, 1);
636
637        let sat_node = g.add_node(NodeType::Saturate);
638        let _ = g.connect(combined, 0, sat_node, 0);
639
640        // Make it grayscale
641        let gray   = g.add_node(NodeType::CombineVec3);
642        let _ = g.connect(sat_node, 0, gray, 0);
643        let _ = g.connect(sat_node, 0, gray, 1);
644        let _ = g.connect(sat_node, 0, gray, 2);
645
646        let alpha = g.add_node(NodeType::ConstFloat(1.0));
647        let out4  = g.add_node(NodeType::CombineVec4);
648        let _ = g.connect(gray,  0, out4, 0);
649        let _ = g.connect(alpha, 0, out4, 1);
650
651        let out = g.add_node(NodeType::OutputColor);
652        g.set_output(out);
653        let _ = g.connect(out4, 0, out, 0);
654        g
655    }
656
657    /// List all available preset names.
658    pub fn all_names() -> Vec<&'static str> {
659        vec![
660            "void_protocol",
661            "blood_pact",
662            "emerald_engine",
663            "corruption_high",
664            "null_fight",
665            "paradox_invert",
666            "fire_elemental",
667            "ice_elemental",
668            "aurora",
669            "static_noise",
670        ]
671    }
672
673    /// Load a preset by name.
674    pub fn by_name(name: &str) -> Option<ShaderGraph> {
675        match name {
676            "void_protocol"   => Some(Self::void_protocol()),
677            "blood_pact"      => Some(Self::blood_pact()),
678            "emerald_engine"  => Some(Self::emerald_engine()),
679            "corruption_high" => Some(Self::corruption_high()),
680            "null_fight"      => Some(Self::null_fight()),
681            "paradox_invert"  => Some(Self::paradox_invert()),
682            "fire_elemental"  => Some(Self::fire_elemental()),
683            "ice_elemental"   => Some(Self::ice_elemental()),
684            "aurora"          => Some(Self::aurora()),
685            "static_noise"    => Some(Self::static_noise()),
686            _ => None,
687        }
688    }
689}
690
691// ── Tests ─────────────────────────────────────────────────────────────────────
692
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_all_presets_compile_without_panic() {
699        for name in ShaderPreset::all_names() {
700            let graph = ShaderPreset::by_name(name).unwrap();
701            assert!(!graph.name.is_empty());
702            assert!(graph.output_node.is_some(), "Preset {} has no output node", name);
703            // Stats sanity
704            let s = graph.stats();
705            assert!(s.node_count > 3, "Preset {} has too few nodes", name);
706        }
707    }
708
709    #[test]
710    fn test_void_protocol_has_parameters() {
711        let g = ShaderPreset::void_protocol();
712        assert!(!g.parameters.is_empty());
713        assert!(g.parameters.iter().any(|p| p.name == "void_zoom"));
714    }
715
716    #[test]
717    fn test_corruption_high_parameters() {
718        let g = ShaderPreset::corruption_high();
719        assert!(g.parameters.iter().any(|p| p.name == "corrupt_cx"));
720        assert!(g.parameters.iter().any(|p| p.name == "corrupt_cy"));
721    }
722
723    #[test]
724    fn test_by_name_unknown_returns_none() {
725        assert!(ShaderPreset::by_name("does_not_exist").is_none());
726    }
727
728    #[test]
729    fn test_all_names_count() {
730        assert_eq!(ShaderPreset::all_names().len(), 10);
731    }
732
733    #[test]
734    fn test_preset_topological_order() {
735        for name in ShaderPreset::all_names() {
736            let graph = ShaderPreset::by_name(name).unwrap();
737            let order = graph.topological_order();
738            assert!(order.is_ok(), "Preset {} has cycles or invalid graph", name);
739        }
740    }
741}