Skip to content

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:

  • ModifierMirror inserts 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 gaps
  • Rotate(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 ModifierCircle on a 24-pixel ring layout produces a single traveling bright dot around the ring
  • ModifierPinwheel(petals=6) + NoiseEffect2D on 16×16 panel produces 6-fold symmetric pattern
  • Unit test covers petals=1 (identity) and petals=8 cases

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:

  • RippleXZ on NoiseEffect2D drives 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 mapping
  • NoiseEffect2D auto-detects 2D layer, runs once per Z slice
  • SphereMoveEffect (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 VirtualLayer with a 3D layout
  • StarFieldEffect(count=20) test: 20 pixels lit per frame, within the cube volume
  • SphereMoveEffect(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 bouncing
  • RubiksCubeEffect renders 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: RainbowEffect on 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 ParticlesEffect gravity. Needs IO module extension for I2C-based IMU. Deferred to R11.
  • Custom 3D layouts via Live Script. .sc scripts that call addLight(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.