Skip to content

Release 3 — Pixel Pipeline Foundations (original plan)

Theme: The minimum viable LED controller — test harness, pin config, one driver, 2D layout on a 16×16 panel, first effects, real LEDs lighting up on ESP32-S3-N16R8.

See moonlight-scope/index.md for what was done vs what remains.


Release Overview

Gap Addressed by
No end-user test+flash+monitor script; preview pipeline unvalidated; ESP32-S3-N16R8 not supported Sprint 1
No formal pin-assignment system Sprint 2 (IO Module)
Each board variant needs its pin map rewritten Sprint 3 (Board Presets)
No real pixel output path Sprint 4 (PhysicalLayer)
No (x,y,z) → LED index mapping Sprint 5 (VirtualLayer + mapping)
No 2D layout module Sprint 6 (LayoutPanel, serpentine)
No real LED driver Sprint 7 (FastLED driver)
No shared effect framework Sprint 8 (Effect framework + first effects)
No global brightness / on-off / color tint Sprint 9 (LightsControl)
Too many required module overrides; hot path not tuned Sprint 10 (module simplification + 500 FPS)

Key design decisions (still current):

  • Unified StatefulModule base. MoonLight's Effect/Modifier/Layout/Driver are separate node types; projectMM keeps one base class and uses category for UI grouping.
  • Dual-core split. Effects on Core 0, drivers on Core 1.
  • Start with 2D, 3D-ready from day one. Coordinates are (x,y,z) everywhere; layouts emit z=0 for 2D.
  • Primary hardware target: ESP32-S3-N16R8. Classic ESP32 kept compiling; PC is the default development loop.
  • Performance target: 500 FPS for a trivial 2D effect on a 16×16 panel (compositing applied, driver output skipped).

Sprint 1 — Test Scripts + ESP32-S3-N16R8 + Preview Pipeline Validation ✅

Done. Delivered as part of actual Release 3. See Release 3 Sprint 8 (deploy system) and Release 3 Sprint 1 (preview pipeline).

Key outcomes: Python deploy system (deploy/) replaces the shell scripts planned here; both boards (esp32dev + esp32s3_n16r8) build, flash, and pass live tests; test_preview_e2e.cpp validates headless preview pipeline; PSRAM + FS controls added to SystemStatusModule.

Metric Value
Tests 179 total, 629 assertions
ESP32-S3-N16R8 fps ~67 K (Network only)
ESP32-S3-N16R8 Flash 26.6% of 4 MB
esp32dev Flash 85.9% of 1.28 MB

Sprint 2 — IO Module v1

Pending.

Goal: first-class pin-assignment system so every hardware-facing module can declare which GPIO it uses without hardcoding pin numbers.

  • IoModule — central StatefulModule owning pin-type descriptors: led_data, onboard_led, button, sensor_generic. Each entry: pin number, type, consumer module ID, modded flag.
  • Pin-claim API: int pin = io->claim("led_data", id()) — returns -1 if unavailable; module logs error and disables itself.
  • maxPower envelope (0–500 W, default 10 W) — stubbed; real clamping in R6 Sprint 6.

Definition of Done: IoModule visible in UI; pin claims deterministic; double-claim + unknown-type covered by tests.


Sprint 3 — Board Presets + ESP32-S3-N16R8 Preset

Pending.

Goal: one click in the UI fills IoModule with the right default pin assignments for the target board.

  • Preset format: JSON blob with board name, chip family, PlatformIO env, default pin assignments. Stored in docs/boards/*.json, bundled into frontend_bundle.h.
  • Initial presets: generic_esp32, esp32_s3_devkit, esp32_s3_n16r8 (primary R3 target — GPIO 16 for LED strip, GPIO 48 for onboard WS2812).
  • POST /api/board applies a preset; modded pins are preserved.
  • Board detection hint: suggest matching preset when chip model matches and IoModule is at defaults.

Definition of Done: picking esp32_s3_n16r8 from UI populates IoModule correctly; modded pins survive; preset JSON validated in tests.


Sprint 4 — PhysicalLayer + channelsD Buffer (3D-Ready) ✅

Done (different architecture). The pixel pipeline was rebuilt in actual Release 3 as EffectsLayer / DriverLayer with a Coord3D coordinate system and a 3D-ready w×h×d pixel buffer. See Release 3 Sprints 3b and 5.

The PhysicalLayer / VirtualLayer naming was superseded; the core ideas (3D-ready flat buffer, domain-agnostic base classes, coordinate-based setPixel) are fully implemented.

Still pending from this sprint: Core 1 FreeRTOS driver task (xTaskCreatePinnedToCore dispatch was wired in R2 Sprint 4 infrastructure but not yet dispatched); channelsDFreeSemaphore buffer-swap protocol.


Sprint 5 — VirtualLayer + Mapping Table ✅

Done (different architecture). Coordinate mapping delivered via Coord3D + GridLayout physical mapping in actual Release 3. See Release 3 Sprint 6.

setPixel(x, y, z, rgb) equivalent via DriverLayer Coord3D blend loop; serpentine wiring in GridLayout. forEachLight iterator equivalent via effect loop over w×h×d.


Sprint 6 — 2D Panel Layout (Serpentine) ✅

Done. Delivered as GridLayout with physical coordinate mapping and serpentine wiring in actual Release 3 Sprint 6. See Release 3 Sprint 6.


Sprint 7 — FastLED Driver (WS2812)

Pending.

Goal: first driver module that drives real LEDs. PC build stubs it.

  • FastLedDriverModule (ESP32-only): claims led_data pin from IoModule; configures FastLED.addLeds<WS2812B, PIN, GRB>; loop() calls FastLED.show() from Core 1 task.
  • PC stub: writes each frame to build-logs/leds.bin for pipeline validation without hardware.
  • Config: chip (WS2812/SK6812/WS2815), color_order (RGB/GRB/BGR).
  • Primary target validation: SolidEffectModule(red) lights the full 16×16 panel on ESP32-S3-N16R8.

Definition of Done: panel lights on primary target; PC writes leds.bin; FastLED footprint ≤ 120 KB flash, ≤ 2 KB RAM.


Sprint 8 — Effect Framework + First Effects

Partially done.

Done: SineEffect, RipplesEffect, LinesEffect ported from MoonLight in actual Release 3 Sprint 1. BrightnessModifier added. See Release 3 Sprint 1.

Still pending:

  • SolidEffectModule — single color across all lights (r, g, b uint8_t controls). Simplest possible effect; hot-path benchmark baseline for Sprint 10.
  • RainbowEffectModule (2D) — hue cycles across the panel with configurable density per axis.
  • Effect authoring target: each effect ≤ 80 LoC.

Sprint 9 — Lights Control Module

Pending.

Goal: global on/off, brightness, and color tint applied after all effects, before the driver.

  • LightsControlModule — permanent singleton. Controls: on (bool), brightness (uint8_t 0–255, default 51 = 20%), r_scale/g_scale/b_scale (uint8_t), bpm (broadcast), intensity (broadcast).
  • Apply stage in DriverLayer::apply(): multiply each channel by brightness × rgb_scale / 255²; zero buffer when on == false.
  • maxPower stub: logs warning if estimated power exceeds IoModule::maxPower; real clamping in R6.
  • Auto-created on first boot (parallel to NetworkModule).

Definition of Done: brightness=0 → black; on=false → black; RGB scale test (SolidEffect(255,255,255) + r_scale=255, g_scale=0, b_scale=0 → pure red).


Sprint 10 — Module Simplification + 500 FPS Tuning + End-to-End Demo

Partially done.

Done: Module simplification audit and StatefulModule interface rationalisation delivered in actual Release 3 Sprints 5 and 7. Mandatory vs optional overrides clearly separated; setProps/setInput/loadState/saveState documented. See Release 3 Sprints 5, 7.

Still pending:

  • 500 FPS hot-path tuning. Profile SolidEffectModule + DriverLayer::apply() + LightsControlModule on ESP32-S3-N16R8 (driver show() skipped). Target: ≥ 500 FPS. Per-stage timings in Scheduler::printTimings(). No-allocations assertion (heap delta = 0 over 1000 ticks).
  • End-to-end LED demo. Default board preset + GridLayout(16×16, serpentine) + SineEffect + FastLedDriverModule; cold flash; monitor-check.py green; photo/GIF in docs/user-guide/first-demo.md.

Release 3 Backlog

  • IoModule → FastLED wiring. FastLED driver claims its pin via IoModule::claim("led_data").
  • Reboot button. button-type control on SystemStatusModule. (Done in actual Release 3 Sprint 9 — remove from backlog.)
  • Implicit parent injection via setInput. When child parent_id matches a parent with a canonical output key, skip the inputs spec.
  • ModuleManager rename → ModuleFactory / ModuleRegistry. (seed from R2 S7)
  • Live Scripts (ESPLiveScript, .sc compiler). Deferred to Release 8.
  • Encryption of sensitive state at rest. Pick up when security requirements are clearer.