Smoothing, but for Everything
Here’s a patch I try to build in every new tool, as a kind of smoke test: wire the mouse position into a Damper, wire the Damper into a circle’s position, and watch the circle lazily chase the cursor.
In Lux until this week, that didn’t work. Damper was a Number node. Mouse position is a Vec2. You had to split the Vec2 into X and Y, put a Damper on each, and recombine. Two extra nodes for the simplest smoothing interaction in the book.
Multiply that by Spring, SmoothDamp, Ease, and Ramp and you’re looking at an entire animation plugin that pretends vectors don’t exist.
What changed
All five of those nodes now accept PinType::Any on their target inputs. Internally they carry four components of state and apply their smoothing logic per-component. If you feed in a Number, only the first component is used. If you feed a Vec2, the first two. Vec3, three. Vec4 and Color, all four.
Damper now does the mouse-chase in one node. Spring bounces Vec3 positions with per-axis physics. SmoothDamp critically-damps a Color over to a target. Ease interpolates Vec4s from A to B with any of the eight curves. Ramp linearly interpolates between two values of any matching type.
Configuration pins stay scalar: stiffness, damping, duration, easing curve. Those are inherently single values. What’s variable is the target you’re smoothing toward, and that’s where multi-component support actually matters.
The output pin problem
The interesting wrinkle is the output pin. If the input is a Vec2, the output should be a Vec2. If the input is a Number, the output should be a Number. A static PinType::Any output works for wire compatibility, but downstream inspectors and type-aware nodes still want to know what the value is.
The fix is a new hook I’m calling dynamic_info. A node can override its reported pin types based on its current runtime state. Damper’s implementation reads the actual type of the last value it processed and reports that on its output pin. Connect a Vec2 wire to the input and the output pin immediately re-reports itself as Vec2. Connect a Color and it becomes Color.
This is the kind of thing that sounds fiddly in isolation but makes the editor feel correct. You wire a Mouse into a Damper into a Translate, and the types line up end to end without you thinking about them. No adapters, no splits, no manual coercion. It just works.
Why ADSR is still Number-only
One exception: ADSR stays scalar. Attack, decay, sustain, and release are fundamentally single values, they describe a one-dimensional envelope shape. Applying them per-component of a Vec3 would imply the Vec3’s X, Y, and Z each have their own independent envelope, which is a different feature (and a weird one). If you want a vector shaped by an ADSR, you multiply the vector by the ADSR’s scalar output. That’s what a Multiply node is for.
The rule I’m trying to hold to: only make a node polymorphic if the math is obviously the same on each component. Damper and Spring and Lerp all pass that test. ADSR doesn’t.
The tests
Each of the five upgraded nodes got a fresh round of tests, 30+ total, covering Vec2 convergence, Color round-trips, save/restore across type changes, and component-wise independence. The save/load format handles the expanded state transparently; old projects that saved a scalar Damper value load as a Vec2 with zeros in the unused components. Nothing breaks.
The patch I wanted
Mouse → Damper → Translate. Three nodes. One wire each. The circle smoothly chases the cursor the way it should have on day one.
That test passes now.