engawa-snow 0.1.2

Flagship snow effect for engawa. Layered parallax snowflakes + cursor-deflection + typing-pulse + accumulation pile, authored in engawa-lisp + WGSL, dispatched through engawa-wgpu. Embeds shader + graph; exposes typed SnowParams uniform and apply() helper for per-frame state push.
Documentation

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 embedded snow.tlisp + snow.wgsl.
  • SnowParams — 64-byte Pod/Zeroable uniform with typed builders for time / intensity / wind / typing-pulse / accumulation / cursor / layer-count, ready for queue.write_buffer.
  • compiled_graph() — feed it to any engawa Dispatcher impl.

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 count
  • Esc → quit

Verification

15 tests:

  • 9 unit tests — SnowParams layout (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.