Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
160 changes: 160 additions & 0 deletions docs/history/assessment-2026-05.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Objective Assessment of the Project

Based on the code, specs (`docs/moonmodules/`), architecture docs, and `docs/plan.md`.

---

## Short Conclusion

This is **no longer a "specs-only" project**. There is a **real end-to-end pipeline** running (layout → layer → effects/modifier → blend/LUT → drivers → HTTP/WebSocket UI), with **serious testing** and **working ESP32 hardware**. The structure aligns well with the v3 philosophy (simple, data-driven, minimal magic).

At the same time, it is still **far from the complete vision** described in the README/architecture (many effects, LED drivers, WiFi, persistence, multi-layer support, parallelism). The README status ("architecture phase, implementation starting") is **outdated**.

**Overall:** strong **technical foundation + process** (~7/10), but the end-user product is still **about halfway there** (~4–5/10 compared to Release 1.0 in `plan.md`).

---

# How Far Along Is It?

## What *is* already done (core system)

| Area | Status |
|------|--------|
| **MoonModule + controls + scheduler** | Complete, consistently used |
| **Pipeline** | LayoutGroup → GridLayout → Layer → effects → modifier → DriverGroup → ArtNet + Preview |
| **Mapping** | MappingLUT, MirrorModifier, BlendMap, 1:1 optimization without extra buffer |
| **Effects** | Rainbow, Noise |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Assessment is inconsistent with this PR's changes.

This assessment document describes features as missing that are actually being delivered in this PR:

  • Line 26 lists only "Rainbow, Noise" effects, but this PR adds 10 new effects (Plasma, Metaballs, Fire, Particles, Checkerboard, Spiral, PlasmaPalette, Ripples, GlowParticles, LavaLamp) per layers 2–5.
  • Line 44 claims "UI type picker + dynamic effect switching" is missing, but layer 7 implements exactly this (UI fetches /api/types, adds picker card, wire add/delete/move actions).
  • Line 47 lists "Ripples" as missing, but RipplesEffect is added in layer 5.
  • Line 102 states "/api/types does not exist yet," but layer 6 adds GET /api/types in HttpServerModule.

Since this document is part of the PR and dated the same month, readers will assume it reflects the post-merge state, making these claims misleading.

Recommendation: Either update the assessment to reflect the state after this PR, or clearly label it as a "pre-PR baseline" snapshot for historical reference.

Also applies to: 44-44, 47-47, 102-102

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/history/assessment-2026-05.md` at line 26, Update the assessment to
match this PR’s post-merge state (or clearly label it as a pre-PR baseline):
replace the Effects entry that currently lists only "Rainbow, Noise" with the
full set added by layers 2–5 (Plasma, Metaballs, Fire, Particles, Checkerboard,
Spiral, PlasmaPalette, Ripples, GlowParticles, LavaLamp), mark that
RipplesEffect is implemented (layer 5), change the "UI type picker + dynamic
effect switching" missing note to present since layer 7 implements the UI and
wiring for /api/types, and remove/adjust the claim that GET /api/types does not
exist because HttpServerModule (layer 6) adds that endpoint; ensure references
to RipplesEffect, HttpServerModule, GET /api/types, and the layer numbers are
updated or add a “pre-PR baseline” header so readers know the snapshot’s intent.

| **Drivers** | ArtNet send, Preview (WebSocket binary frames) |
| **HTTP API** | `/api/state`, `/api/control`, `/api/system`, module CRUD (`POST/DELETE /api/modules`) |
| **Web UI** | Vanilla JS, WebSocket, sliders/toggles/text, **WebGL 3D preview** |
| **Desktop** | Builds and runs (`mmv3`, port 8080) |
| **ESP32** | Ethernet (Olimex Gateway), same pipeline, live scenarios on hardware |
| **Tooling** | MoonDeck, platform-boundary check, scenario runners (in-process + live) |
| **Tests** | 10 unit test files + 5 JSON scenarios + live scenarios (desktop + ESP32) |

The promoted specs in `docs/moonmodules/` (18 total) mostly match the implementation in `src/`, which is unusually good for an agent-driven project.

---

## What is still missing (or intentionally postponed)

From `docs/plan.md` (items 9–13) and draft specs:

- **System MoonModule** as an actual module (diagnostics partially exist in `/api/system`, but not as a MoonModule in the tree)
- **WiFi**, **config persistence**, **UI type picker** + dynamic effect switching from the browser
- **README quick-start** (you already got it working; the repo docs have not caught up)
- **WS2812 / APA102 / direct DMX output** — currently only ArtNet network output
- **Many effects/layouts** from `moonmodules_draft/` (Wheel, GameOfLife, Ripples, Rotate, ...)
- **Multi-layer support** (architecture docs describe Layer A/B/C; code currently uses a single `Layer` in `main.cpp`, and `DriverGroup` comments reference "multi-layer later")
- **Parallelism / core affinity** — documented, not implemented in the scheduler
- **Teensy / Raspberry Pi support** — only mentioned in docs, no `src/platform/teensy` etc.

### Rough completion estimates

- **Vertical slice (single installation, ArtNet + browser):** ~**65–70%**
- **Full architectural vision** (all platforms, all protocols, rich effects library): ~**25–30%**
- **Release 1.0** ("flash, WiFi, browser, settings persistence"): item 8 seems complete; 9–13 still open → ~**50%** toward that milestone

---

# How Well Is It Built?

## Strong Points

### 1. Architecture and discipline

`CLAUDE.md`, `architecture.md`, promoted specs, and `check_platform_boundary.py` create a **real engineering framework**. Platform-specific code is isolated in `src/platform/`; the rest compiles everywhere. That prevents the typical drift seen in v1/v2.

### 2. The MoonModule pattern actually works

One lifecycle (`setup` → `onBuildControls` → `onAllocateMemory` → `loop` / `loop20ms`), generic children, `ModuleRole`, and a runtime CRUD factory. Learnable and extensible without requiring UI rewrites for every effect.

### 3. Memory-conscious design

The `memory-1to1` and `memory-lut` scenarios prove intentional memory decisions (no LUT buffer for 1:1 mappings, but one for mirror mappings). `performance.md` measures both desktop and ESP32 performance — unusually useful this early.

### 4. Test strategy

Unit → scenario → live HTTP tests is **mature** for embedded/C++. Hardware verification on 128×128 without PSRAM is explicitly documented in `testing.md`.

### 5. UI philosophy

No npm/runtime dependency chain in the UI; controls are rendered from module state. WebSocket + debounce + in-place updates follow the original UI spec from v1 — much of it is already implemented in `app.js`.

### 6. Code style

Header-only modules, `constexpr`, `std::span`, `-Werror`, and no TODOs in `src/`. Fits the "understandable in 30 seconds" philosophy per file (except perhaps `HttpServerModule.h`).

---

## Weak Points / Technical Debt

### 1. `HttpServerModule.h` is a monolith

HTTP, WebSocket, JSON parsing, static files, CRUD — everything lives in one large header. Functional, but the opposite of "single-file simplicity" from a maintenance perspective.

### 2. Rebuild propagation is coarse

Every control change calls `scheduler_->rebuild()` → triggering `onAllocateMemory()` on **all** top-level modules. The architecture docs describe fine-grained propagation; this is not there yet.

### 3. The layer pipeline is intentionally limited

One modifier during LUT build; multiple sequential effects are supported, but the default stack in `main.cpp` is fixed. Runtime CRUD exists via the API, but the **UI** lacks a type picker (`/api/types` does not exist yet).

### 4. ESP32 performance

According to `performance.md`, at 128×128, roughly **51% of the tick time is ArtNet UDP** — no pacing/limiting yet as recommended in `decisions.md`. Functionally fine (~17 FPS), but not scalable to "10,000+ LEDs" on this chip without optimization.

### 5. Documentation vs reality

The README still says "implementation starting"; `plan.md` describes live scenarios as "next steps" while `testing.md` states they already pass on hardware. This is confusing for new developers.

### 6. Platform breadth

Vision = ESP32 + desktop + Teensy + Raspberry Pi.
Implementation = desktop + ESP32 (Ethernet). The rest remains roadmap material.

---

# Evaluation by Dimension

| Dimension | Score | Notes |
|-----------|------|------|
| **Architecture (design)** | 8/10 | Clear core/domain separation, MoonModule everywhere, strong docs |
| **Core implementation** | 7/10 | Pipeline works, tested, hardware-proven |
| **Extensibility** | 7/10 | Factory + specs; HttpServer/UI not yet fully generic for "everything from browser" |
| **Testing & quality** | 8/10 | Strong for project size; includes live + memory scenarios |
| **Product/UX (end-user)** | 5/10 | Works locally; no WiFi flash flow, persistence, or type picker |
| **Performance (ESP32)** | 6/10 | 128×128 works; ArtNet dominates; no LED DMA path |
| **Documentation** | 7/10 | Excellent internally; README/plan are out of sync |

---

# Comparison to Earlier Iterations (`docs/history/`)

This feels like a **deliberate response to v1/v2 bloat**:

- Less "framework for the framework"
- Specs before code
- Strict platform boundaries and checklists
- One working pipeline instead of 60 effects without tests

The project is beyond the "prototype" stage, but not yet at MoonLight/WLED-level breadth in terms of effects and drivers.

---

# What I Would Prioritize (for reference only)

As a product owner, aligned with `plan.md`:

1. Update README + quick-start
2. System MoonModule + WiFi (for Release 1.0)
3. Config persistence
4. UI type picker + module switching (API is partially there already)
5. ArtNet pacing/batching on ESP32

---

# One-Sentence Summary

The project is **technically solid and well-controlled** for an early v3 cycle, with a **working LED pipeline and serious testing**; however, it is **not yet a complete end-user product** where "a customer flashes an ESP32 and starts playing with effects," and the **README significantly understates** how far the implementation already is.
19 changes: 19 additions & 0 deletions docs/moonmodules/light/effects/CheckerboardEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Checkerboard 2D Effect

Animated checker pattern with two configurable hues.

## Controls

- `enabled` (bool) — from `EffectBase`
- `cell_size` (uint8_t, default 4, range 1-32) — cell width/height in grid units
- `bpm` (uint8_t, default 60, range 1-255) — phase shift speed (cells appear to move)
- `hue_a` (uint8_t, default 0) — colour of even cells
- `hue_b` (uint8_t, default 128) — colour of odd cells

## Rendering

Checker bit from `(x/cell + y/cell + phase) & 1`. `dynamicBytes()` = 0.

## Tests

[Module test](../../../testing.md#stateless-effects) — non-zero output, spatial variation.
40 changes: 40 additions & 0 deletions docs/moonmodules/light/effects/FireEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Fire 2D Effect

Classic demoscene fire simulation on the XY plane. Maintains a `width x height` heat field; sparks spawn at the bottom row, drift upward with cooling, and are mapped to a black-red-yellow-white palette.

## Controls

- `enabled` (bool, default true) — inherited from `EffectBase`
- `cooling` (uint8_t, default 55, range 10-200) — heat loss per frame (higher = shorter flames)
- `sparking` (uint8_t, default 120, range 50-255) — probability of new sparks at the bottom row
- `hue_shift` (uint8_t, default 0, range 0-255) — rotate the fire palette around the colour wheel (0 = classic fire, 96 = green ghost-fire, 160 = blue plasma-fire)

## Rendering

Per frame (in this order):

1. **Cool** every cell by a small random amount.
2. **Rise** — each row averages from the row below (4-tap neighbourhood), heat propagates from `y = h-1` up toward `y = 0`.
3. **Sparks** — up to 4 random sparks at the bottom row each frame, gated by `sparking`.
4. **Render** the heat field to RGB via the fire palette (or hue-rotated HSV when `hue_shift != 0`).

Integer-only, no floats. Internal PRNG (LCG) avoids `rand()`.

## Memory

Allocates `width * height` bytes for the heat buffer in `onAllocateMemory()` when `enabled` is true. Freed in `teardown()` and when disabled. Toggling `enabled` triggers a scheduler rebuild that (re)allocates.

| Logical size | Heat buffer |
|--------------|-------------|
| 64x64 | 4 KB |
| 128x128 | 16 KB |

`dynamicBytes()` reports the live size.

## Tests

[Module test: FireEffect](../../../testing.md#fire) — buffer becomes non-zero after several frames of sparking.

## Prior art

Standard demoscene fire (Lode's tutorials, FastLED's `Fire2012`). Adapted to the integer-only, no-Arduino style of this codebase.
21 changes: 21 additions & 0 deletions docs/moonmodules/light/effects/GlowParticlesEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Glow Particles 2D Effect

Soft-glowing particles rendered as a metaball field. Particles move with independent velocities and bounce off the edges; the per-pixel field summation produces chaotic organic blobs — like `MetaballsEffect` with more freedom of movement.

## Controls

- `enabled` (bool) — from `EffectBase`
- `count` (uint8_t, default 5, range 1-8) — number of glow sources
- `speed` (uint8_t, default 60, range 1-255) — movement speed
- `radius` (uint8_t, default 24, range 4-64) — influence radius (larger = more merging)
- `hue_shift` (uint8_t, default 0, range 0-255) — global hue rotation

## Rendering

Position update uses 12.4 fixed-point arithmetic. Per pixel: `field += (radius² × 64) / (dx² + dy² + 1)` across all active particles. Brightness clamps to 255; hue derives from the field magnitude.

No heap allocations. Per-particle state: 8 bytes × 8 = 64 bytes.

## Tests

[Module test](../../../testing.md#stateless-effects) — non-zero output, spatial variation.
18 changes: 18 additions & 0 deletions docs/moonmodules/light/effects/LavaLampEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Lava Lamp 2D Effect

Three slow blobs whose summed field is mapped through a black → red → orange → yellow → white palette. Atmospheric, fluid look — like a real lava lamp rather than the bright HSV of `MetaballsEffect`.

## Controls

- `enabled` (bool) — from `EffectBase`
- `bpm` (uint8_t, default 8, range 1-64) — orbit speed in beats per minute
- `radius` (uint8_t, default 36, range 8-80) — blob influence radius
- `intensity` (uint8_t, default 200, range 64-255) — how strongly the field maps into the palette

## Rendering

Three integer-orbited blobs (`sin8` for x/y at independent phases and speeds). Per pixel: `field += (radius² × 64) / (dx² + dy² + 1)` summed over the blobs, then `palette_[(field × intensity) >> 8]`. Palette is a 256-entry constexpr table in flash (768 bytes). No heap allocations.

## Tests

[Module test](../../../testing.md#stateless-effects) — non-zero output, spatial variation.
30 changes: 30 additions & 0 deletions docs/moonmodules/light/effects/MetaballsEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Metaballs 2D Effect

Four "blobs" moving on the XY plane via integer sin/cos, with a metaball field summation per pixel. Visually similar to a lava lamp — blobs fluidly merge and separate.

## Controls

- `enabled` (bool, default true) — inherited from `EffectBase`
- `bpm` (uint8_t, default 30, range 1-255) — orbit speed in beats per minute
- `radius` (uint8_t, default 28, range 4-64) — ball influence radius (larger = more merging)
- `hue_shift` (uint8_t, default 0, range 0-255) — rotate the resulting hue

## Rendering

Per frame:
- Compute 4 ball positions `(bx, by)` using `sin8()` at different phases and orbit speeds. Stateless apart from a phase accumulator (BPM-stable).

Per pixel:
- For each ball: `field += (radius^2 * 64) / (dx^2 + dy^2 + 1)`
- Brightness = clamped `field`; hue = `(field >> 1) + hue_shift`
- Output via `hsvToRgb(hue, 240, brightness)`

Four divisions per pixel — acceptable for desktop and ESP32 LX7. No floats, no heap (`dynamicBytes()` = 0).

## Tests

[Module test: MetaballsEffect](../../../testing.md#metaballs) — non-zero output, spatial variation.

## Prior art

Classic demoscene effect (1980s). Same integer field-summation technique as countless WLED ports.
40 changes: 40 additions & 0 deletions docs/moonmodules/light/effects/ParticlesEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Particles 2D Effect

A swarm of particles drifting on the XY plane with persistent trails. Each particle has fixed-point position, velocity, and hue. A private RGB trail buffer is faded each frame, particles are drawn on top, and the trail buffer is copied to the layer buffer (which the Layer clears every frame).

## Controls

- `enabled` (bool, default true) — inherited from `EffectBase`
- `count` (uint8_t, default 32, range 1-64) — number of active particles
- `speed` (uint8_t, default 80, range 1-255) — velocity multiplier
- `fade` (uint8_t, default 240, range 200-255) — trail persistence (255 = no fade, 200 = quick fade)
- `hue_shift` (uint8_t, default 0, range 0-255) — rotate all particle hues

## Rendering

Per frame:

1. **Fade** every byte in the trail buffer via `scale8(byte, fade)` (integer multiply, no float).
2. **Update + draw** — for each active particle: advance position using 12.4 fixed-point math, bounce off edges, draw RGB into the trail buffer.
3. **Copy** the trail buffer into the layer buffer.

Maximum particles is `MAX_PARTICLES = 64` (a compile-time constant). Particle state is a fixed array — no heap allocation for the particle list itself.

## Memory

Allocates `width * height * channelsPerLight` bytes for the persistent trail buffer in `onAllocateMemory()` when `enabled` is true. Freed in `teardown()` and when disabled.

| Logical size | Trail buffer (RGB) |
|--------------|--------------------|
| 64x64 | 12 KB |
| 128x128 | 48 KB |

Particle list (`64 * 8` bytes) is part of `sizeof(ParticlesEffect)`, not `dynamicBytes()`. `dynamicBytes()` reports only the trail buffer.

## Tests

[Module test: ParticlesEffect](../../../testing.md#particles) — buffer becomes non-zero after one frame (particles draw immediately).

## Prior art

Classic "snow"/"stars"/"fireflies" effect from many LED firmwares. The fade-and-draw approach (a.k.a. *afterimage*) matches FastLED's `fadeToBlackBy` idiom.
40 changes: 40 additions & 0 deletions docs/moonmodules/light/effects/PlasmaEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Plasma 2D Effect

Animated plasma pattern from four summed sine waves on orthogonal and diagonal axes. Default effect in the desktop and ESP32 pipeline.

## Controls

- `bpm` (uint8_t, default 30, range 1-255) — animation speed in beats per minute
- `scale_x` (uint8_t, default 16, range 1-64) — horizontal wave length in grid cells (`step = 256 / scale_x`)
- `scale_y` (uint8_t, default 16, range 1-64) — vertical wave length in grid cells
- `hue_shift` (uint8_t, default 0, range 0-255) — rotates the entire color wheel

## Rendering

Four `sin8` lookups per pixel (256-byte constexpr LUT in flash, no float, no heap). Waves:

- Horizontal: `sin8(x * step_x + t1)`
- Vertical: `sin8(y * step_y + t2)` — hoisted per row
- Diagonal: `sin8(x * step_x + y * step_x - t3)`
- Anti-diagonal: `sin8(x * step_y + 128 - y * step_y + t1)`

Sum averaged (`>> 2`), add `hue_shift`, map via `hsvToRgb(hue, 255, 255)`.

### Performance

- No division or modulo in the inner loop — `step_x`/`step_y` computed once per frame
- Nested `for (y) for (x)` with row pointer — no `i % w` per pixel
- Y-dependent terms hoisted outside the x-loop
- `dynamicBytes()` = 0 — no `onAllocateMemory` override

Phase accumulator matches NoiseEffect pattern — BPM changes do not jump the animation.

## Tests

[Module test: PlasmaEffect](../../../testing.md#plasma) — non-zero output, spatial variation, differs from NoiseEffect.

Default pipeline uses Plasma + MirrorModifier (see `src/main.cpp`).

## Prior art

Classic demoscene plasma effect (sum of sines). Integer sin8 LUT approach matches FastLED-style tables. No direct v1/v2 module port — simpler than NoiseEffect (no hash/bilinear).
18 changes: 18 additions & 0 deletions docs/moonmodules/light/effects/PlasmaPaletteEffect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Plasma Palette 2D Effect

Same four-sine plasma field as `PlasmaEffect`, but colours come from a 256-entry fire-ocean RGB palette in flash instead of `hsvToRgb`.

## Controls

- `enabled` (bool) — from `EffectBase`
- `bpm` (uint8_t, default 30, range 1-255)
- `scale_x` (uint8_t, default 16, range 1-64)
- `scale_y` (uint8_t, default 16, range 1-64)

## Rendering

Plasma index `((s1+s2+s3+s4)>>2)` maps through `palette_[256]` (768 bytes flash). `dynamicBytes()` = 0.

## Tests

[Module test](../../../testing.md#stateless-effects) — non-zero output, spatial variation.
Loading