Skip to content

Architecture

The agreed-up-front architecture contract: what projectMM is designed to be. A design described here is committed, meaning this is the intended behavior and code is written toward it, rather than optional or undecided.

Coding conventions live in coding-standards.md; how to build and run lives in building.md; what is tested lives in testing.md.

The problem

Driving a large LED installation sets three constraints at once.

  • Scale: tens of thousands of lights, refreshed fifty times a second, on a chip with a few hundred kilobytes of RAM.
  • Variety: strips, panels, DMX fixtures and moving heads, each with its own wire protocol and its own definition of a pixel.
  • Change: you rearrange the show while it runs, with no reboot and no recompile.

A fixed pipeline holds the frame rate but cannot be rearranged. A scriptable one rearranges but cannot hold the frame rate. projectMM meets all three with one uniform building block on a known lifecycle, a domain-neutral core that owns the hard constructs once, and a light domain that stays simple on top of it.

The parts, and how they sit

flowchart LR
    mm["<b>MoonModule</b><br/><i>the one building block</i>"]
    core["<b>MoonCore</b><br/><i>runtime, platform, services</i>"]
    light["<b>MoonLight</b><br/><i>layouts, effects,<br/>modifiers, drivers</i>"]
    live["<b>MoonLive</b><br/><i>scripts compiled<br/>on the device</i>"]

    mm --> core --> light
    live --> light

    base["<b>MoonBase</b> · <i>the second boot image</i>"]
    inst["<b>MoonInstaller</b> · <i>firmware, deviceModel, board</i>"]
    cloud["<b>MoonCloud</b> · <i>opt-in stats and talk</i>"]
    deck["<b>MoonDeck</b> · <i>the developer console</i>"]

    core -.-> base
    core -.-> inst
    core -.-> cloud
    core -.-> deck

    classDef po fill:#2d3561,stroke:#7b88c9,color:#fff
    classDef agent fill:#3d2d61,stroke:#a07bc9,color:#fff
    classDef check fill:#1f4d3d,stroke:#5fb89a,color:#fff
    classDef gate fill:#4d3d1f,stroke:#c9a95f,color:#fff
    class mm po
    class core,light agent
    class live check
    class base,inst,cloud,deck gate
Part What it decides
MoonModule The one building block: lifecycle, controls, persistence, how modules exchange data and trigger each other
MoonCore The domain-neutral runtime: platform abstraction, services, several devices as one system
MoonLight The light domain: the pipeline, 3D, effects, modifiers, drivers, and the memory strategy that bounds them
MoonLive Scripts compiled to native code on the device
MoonBase The second boot image, and why an update cannot leave a half-written app
MoonInstaller Firmware, deviceModel and board: three words for three different things
MoonCloud The opt-in server side, and the only server a device talks to
MoonDeck One script per task, two front ends

Core and light domain

The system is two layers, separated as much as practical.

MoonCore owns the MoonModule base, controls, scheduling, persistence, the platform abstraction and the system services. It is domain-neutral and knows nothing about lights.

MoonLight owns light values, layouts, layers, mapping, blending, effects, modifiers, LED drivers and the network protocols. It is built on top of the core.

When mixing is needed for performance or simplicity it is an explicit decision, choosing minimalism over separation rather than blurring the boundary by accident. Core earns growth only by gaining a recognizable, reusable primitive that many modules lean on: a streaming write, a positional read, a bounded arena, a recursive JSON reader. A one-off helper for a single caller belongs with that caller.

Tag emoji legend

Catalog pages tag each module with its role and its origin. The legend lives with the pages that use it, in the light catalog.