engawa-snow
Flagship snow effect for engawa — end-to-end demonstration of engawa-lisp authoring + engawa-wgpu dispatch.
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ snow.tlisp │──► │ engawa::Render- │──► │ engawa-wgpu │
│ snow.wgsl │ │ Graph (compiled) │ │ pipeline │
└──────────────┘ └──────────────────┘ └──────────────┘
└─── embedded via include_str! ───────────┘
What you get
SnowEffect::new()— one call, compiles the snow render graph from the embeddedsnow.tlisp+snow.wgsl.SnowParams— 64-bytePod/Zeroableuniform with typed builders for time / intensity / wind / typing-pulse / accumulation / cursor / layer-count, ready forqueue.write_buffer.compiled_graph()— feed it to any engawaDispatcherimpl.
What the snow does
Three parallax layers of snowflakes (near / mid / far), each with:
- independent fall speed + horizontal sway + wind drift
- hashed jitter per tile cell (no buffers, no textures — pure procedural generation)
- depth-tinted color (far layer cooler + dimmer)
- near layer gets a bloom halo + sparkle around each flake
Reactions the operator can drive via SnowParams:
| Knob | Effect |
|---|---|
cursor |
near-layer flakes deflect around the cursor in a soft ring |
typing_pulse |
brief brightness + scale boost on every keystroke (caller decays per frame) |
wind |
horizontal drift, layer-depth-weighted |
accumulation |
snow pile builds up at the bottom, modulated by value-noise contour |
intensity |
master gain |
layer_count |
1 / 2 / 3 layers for perf vs density |
Final pass is vignetted + cool color-graded.
Run the live demo
cargo run --example snow_window
Interactions in the window:
- mouse motion → cursor deflection
- left click → typing pulse
[/]→ wind − / +-/=→ accumulation − / +1/2/3→ layer countEsc→ quit
Verification
15 tests:
- 9 unit tests —
SnowParamslayout (64 bytes exactly,Pod-compatible), builder clamping, pulse-takes-max semantics, graph compilation, node ordering, material binding, shader substitution, embedded WGSL anchors - 6 headless GPU tests (feature
gpu_tests) — non-black pixels, byte determinism across runs, time uniform drives motion, cursor uniform changes local pixels, typing pulse brightens, accumulation paints bottom pile
cargo test
cargo test --features gpu_tests
Why a separate crate
Per the pleme-io macros-everywhere prime directive: the
"effect-as-library" shape this crate establishes — EFFECT_TLISP +
EFFECT_WGSL constants, EffectParams Pod struct with clamping
builders, Effect::new() returns a compiled graph — is the template
for the next ones (engawa-rain, engawa-crt, engawa-bloom,
engawa-fire, …). Extracting now means consumer N+1 imports a typed
crate instead of copy-pasting.
License
MIT.