Release 5 — Modifiers & 3D¶
Theme: Release 4 mastered 2D. Release 5 decouples effects from fixtures: modifiers (mirror, rotate, multiply…) transform an effect's output without touching the effect itself, and 3D layouts turn a 2D effect into a cube, sphere, or any shape. By the end, a single 1D effect can drive a 3D LED cube through a modifier chain.
Release Overview¶
| Gap | Addressed by |
|---|---|
| Effects and fixtures are tightly coupled — a 1D effect can't run on a 2D matrix without rewriting | Sprints 1–5 (modifier framework + set) |
| Only 1D and 2D layouts; no cube/sphere/custom 3D | Sprints 6, 9 (3D layouts) |
| No 3D-native effects | Sprints 7–8 (3D effects) |
| Preview UI only shows a flat grid | Sprint 10 (Live Monitor 3D view) |
| Concurrent layer cap still 4 | Raised to 16 during this release (in Sprint 1 setup) |
Sprint 1 — Modifier Framework + Mirror¶
Scope¶
Goal: introduce the modifier concept — a module that sits between an effect and its layer, rewriting each setPixel(x,y,z) call before it hits the mapping table.
Part A — ModifierModule. Documented pattern (not a new base class, same as effects): a StatefulModule positioned as a child of a VirtualLayer; overrides setInput("parent_layer") and provides its own setPixel that transforms coordinates before forwarding.
Part B — ModifierMirror. Controls: axis (X/Y/Z/XY/XZ/YZ/XYZ). Mirrors coordinates around the axis midpoint. Zero allocation — pure coordinate arithmetic.
Part C — Raise layer cap to 16. Required because each modifier adds a layer-like indirection; 4 is too tight for serious chains. Cap comes from a single constexpr change + PSRAM budget recheck.
Definition of Done:
ModifierMirrorinserts between effect and layer; enabling it mirrors the output- Chain test: mirror→mirror = identity (verified pixel-by-pixel)
- 16 layers run on ESP32-S3 at 50 Hz with the R4 test suite green
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 2 — Modifiers: Multiply, Rotate¶
Scope¶
Goal: two more modifiers that enable kaleidoscope-class visuals.
Part A — ModifierMultiply. Controls: count_x, count_y. Tiles the effect output N×M times across the canvas. Each tile independent — pure index wrap-around.
Part B — ModifierRotate. Controls: angle (0–360, degree), center_x, center_y (percentages). Rotates coords around the center each frame. Uses FastLED sin8/cos8 8-bit trigonometry — no float in the hot path.
Definition of Done:
Multiply(2,2)on a 16×16 panel → effect tiled 4× with no gapsRotate(90°)+Mirror(X)=Mirror(Y)(geometric identity test)- Per-pixel modifier cost ≤ 1 µs measured on ESP32-S3
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 3 — Modifiers: Transpose, Checkerboard¶
Scope¶
Goal: two structural modifiers for reshaping output.
Part A — ModifierTranspose. Swaps two axes (XY, XZ, YZ). Makes a 1D effect into a 2D stripe pattern when transposed onto a 2D layout.
Part B — ModifierCheckerboard. Tile-based pattern gate: every other cell of an NxM grid is blanked. Controls: cell_width, cell_height, invert.
Definition of Done:
- Transpose XY on a 1D rainbow + 2D layout → horizontal rainbow stripes
- Checkerboard 8×8 produces a true 2×2 macro-grid of 8×8 effect tiles
- Both verified with render-determinism test (same tick + config → same pixels)
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 4 — Modifiers: Circle, Pinwheel¶
Scope¶
Goal: two projection modifiers that take 1D/2D effects and remap them radially.
Part A — ModifierCircle. Remaps a 1D X-axis effect around a circle — classic "moving light around a ring." Controls: center_x, center_y, radius_pct.
Part B — ModifierPinwheel. 1D/2D → 2D/3D with controls: swirl, rotation_symmetry (n-fold), petals, ztwist. MoonLight's most-used modifier.
Definition of Done:
- 1D sine effect through
ModifierCircleon a 24-pixel ring layout produces a single traveling bright dot around the ring ModifierPinwheel(petals=6)+NoiseEffect2Don 16×16 panel produces 6-fold symmetric pattern- Unit test covers
petals=1(identity) andpetals=8cases
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 5 — Modifier: RippleXZ¶
Scope¶
Goal: the last — and geometrically richest — modifier from MoonLight's set.
Part A — ModifierRippleXZ. 1D/2D → 2D/3D. Controls: shrink (radial contraction per Z layer), towards_x, towards_z. Projects a 2D effect onto successive Z slices with shrinking radius.
Part B — Chain validation. Test modifier chain: Effect → Mirror → Multiply → RippleXZ → Layer. Assert frame output is deterministic and within 50 Hz budget on 16×16×16 (4 K lights).
Definition of Done:
RippleXZonNoiseEffect2Ddrives a 16×16×16 cube with visible depth ripples- Chain of 4 modifiers ≤ 200 µs per frame on ESP32-S3
- RippleXZ documented with a side-view diagram in
docs/modules/modifiers.md
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 6 — 3D Layout: Cube + Dimensionality Auto-Detect¶
Scope¶
Goal: the first 3D layout; the mapping system automatically detects Z-axis use and reports 3D to effects that opt in.
Part A — LayoutCube. Controls: side_length. Generates side³ lights mapped in a standard 6-face layout with configurable wiring order per face.
Part B — Dimensionality propagation. VirtualLayer recomputes dimensionality after layout rebuild. Effects query it in setup() and can branch:
- 1D effect on 3D layout → runs on X axis of first Z slice
- 2D effect on 3D layout → repeats on each Z slice
- 3D effect on 2D layout → flattens Z=0
Part C — Multi-pin face output. Each face claims its own led_data pin (via LayoutMultiPanel pattern from R4 S2). Prep work for parallel drivers in R6.
Definition of Done:
LayoutCube(side=8)produces 384-pixel mappingNoiseEffect2Dauto-detects 2D layer, runs once per Z sliceSphereMoveEffect(Sprint 7) auto-detects 3D, runs fully 3D
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 7 — 3D Effects: StarField, SphereMove¶
Scope¶
Goal: two 3D-native effects that need genuine volumetric coordinates.
Part A — StarFieldEffect. N stars with random (x,y,z) and velocity; each ticks forward; respawns when it leaves the volume. Controls: count, speed, twinkle.
Part B — SphereMoveEffect. Moving sphere rendered volumetrically (pixels inside the sphere lit, outside dark). Controls: radius, speed, wrap.
Definition of Done:
- Both effects spawn as children of
VirtualLayerwith a 3D layout StarFieldEffect(count=20)test: 20 pixels lit per frame, within the cube volumeSphereMoveEffect(radius=2)produces a visible moving sphere on 8×8×8 cube
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 8 — 3D Effects: Particles, Rubik's Cube¶
Scope¶
Goal: two richer 3D effects; Particles lays groundwork for IMU-driven effects in R11.
Part A — ParticlesEffect. Physics-lite particle system: count, gravity (x,y,z vector), bounce, trail fade. Each particle is (position, velocity, color, age).
Part B — RubiksCubeEffect. Renders a 3×3×3 Rubik's cube with face rotations animated. Mostly a showcase effect; low compute, high visual impact.
Definition of Done:
ParticlesEffect(count=50, gravity=-1_y)shows 50 particles falling and bouncingRubiksCubeEffectrenders correctly on 9×9×9 cube (3× scale per face); random face rotations every 2 seconds- Particles footprint: per-particle state ≤ 16 bytes
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 9 — 3D Preset Layouts: Sphere, Human-Sized Cube, Car Lights¶
Scope¶
Goal: three concrete preset layouts from MoonLight's field-tested set.
Part A — LayoutSphere. Approximation of a sphere with user-specified pole + equator density. Controls: latitudes, longitudes.
Part B — LayoutHumanCube. MoonLight's 2m × 2m × 2m walk-in cube preset; translates to scaled coords but honors aspect ratio.
Part C — LayoutCarLights. Linear + curved strips representing headlight + taillight + underbody.
Definition of Done:
- All three registered; spawnable from the frontend dropdown with a single click
- Each layout's pixel count, wiring order, and pin requirements documented in
docs/modules/layouts.md - Minimum viable visual check:
RainbowEffecton each produces a visually coherent result
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Sprint 10 — Live Monitor 3D View¶
Scope¶
Goal: extend R1's WebGL previewer to render 3D layouts — orbit camera, picked pixel info, per-layer isolation.
Part A — Three.js-lite integration. Minimal WebGL 3D renderer embedded in frontend. Reads the mapping table + live pixel stream via existing binary WebSocket channel.
Part B — Orbit camera. Mouse drag = orbit; scroll = zoom. Reset view button.
Part C — Pixel picker. Click a rendered pixel → UI panel shows (x,y,z), physical channel index, current color. Debug aid for mapping issues.
Part D — Per-layer isolation in preview. Checkboxes to hide individual layers from the preview (affects preview only, not LED output).
Definition of Done:
- 3D cube (8³) renders smoothly; 60 FPS browser-side on a modern laptop
- Pixel picker returns correct (x,y,z) for at least 10 sample pixels
- Visual regression: 2D layouts still render correctly in the same previewer
Result¶
To be completed after implementation.
Retrospective¶
To be completed after implementation.
Release 5 Backlog¶
- IMU-driven effects. Gyroscope + accelerometer input affecting
ParticlesEffectgravity. Needs IO module extension for I2C-based IMU. Deferred to R11. - Custom 3D layouts via Live Script.
.scscripts that calladdLight(x,y,z)freeform. Pick up in R8 when Live Scripts land. - Bounded modifiers. A modifier that applies only within a sub-region. Pick up when operator demand surfaces.
- Modifier chain reordering UI. Drag-reorder modifier stack (currently order = JSON array order). Pick up after R10 UI polish.