Skip to main content

effect-livecoding

Write, debug, and tune a Hypercolor lighting effect against a running daemon in a tight loop: install an HTML page, apply it once, read its script errors with line numbers in your own file, look at the rendered canvas, patch controls live, and hot swap new versions without re-applying. Use this whenever someone asks you to create, write, design, port, fix, or iterate on a Hypercolor effect, an RGB or LED animation, or a lighting visual; when an effect "looks wrong", "is black", "froze", "only fills part of the strip", or "ignores its slider"; or when you need to see what Hypercolor is rendering right now. Works through the MCP tools (install_effect, capture_canvas, get_effect_console, adjust_controls) or the `hypercolor` CLI (effects install, capture, console, patch); it never needs the source tree.

Source facts

Repository
hyperb1iss/hypercolor
Last source activity
October 17, 2026 at 22:14
Detected SKILL.md language
English
Stars
32
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
2 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
effect-livecoding
description
Write, debug, and tune a Hypercolor lighting effect against a running daemon in a tight loop: install an HTML page, apply it once, read its script errors with line numbers in your own file, look at the rendered canvas, patch controls live, and hot swap new versions without re-applying. Use this whenever someone asks you to create, write, design, port, fix, or iterate on a Hypercolor effect, an RGB or LED animation, or a lighting visual; when an effect "looks wrong", "is black", "froze", "only fills part of the strip", or "ignores its slider"; or when you need to see what Hypercolor is rendering right now. Works through the MCP tools (install_effect, capture_canvas, get_effect_console, adjust_controls) or the `hypercolor` CLI (effects install, capture, console, patch); it never needs the source tree.
# Effect Livecoding You can see what you render and read what it throws, so never declare an effect done from the source alone. Every change ends with a console read and a capture. ## The loop | Step | MCP | CLI | | --- | --- | --- | | 1. Install the page | `install_effect {html, file_name}` | `hypercolor effects install fx.html` | | 2. Apply it, once | `set_effect {query: <id>}` | add `--activate` to the install | | 3. Read errors | `get_effect_console {after: <console_after>, level: "warn"}` | `hypercolor effects console --after <N> --level warn` | | 4. Look at it | `capture_canvas {frames: 4, interval_ms: 200}` | `hypercolor effects capture -o fx.png --frames 4 --interval-ms 200`, then view the files | | 5. Tune live | `adjust_controls {zone, layer, values}` | `hypercolor effects patch --param speed=2` | | 6. Edit and reinstall | `install_effect` with the same `file_name` | `hypercolor effects install fx.html` | Both install paths return `console_after`, a console cursor taken just before the page loads. Pass it to step 3 so you read only what this version printed. Give the page about a second to load and throw before you read. Without MCP or the CLI, the REST equivalents are `POST /api/v1/effects/install` (multipart field `file`), `GET /api/v1/effects/console?after=N&level=warn`, and `GET /api/v1/canvas/frame?format=png&width=320`. ## How reloads behave - **Keep one file name for the whole session.** The effect id comes from the installed path, so reinstalling the same name replaces the effect in place and a layer already running it reloads the new page on its own. A new name makes a second effect that nothing is running. - **Do not re-apply after a reinstall.** `set_effect` and `--activate` replace the zone's whole layer stack and reset every control to its default. - **Every reinstall restarts the page.** Time, particles, and any other script state start over. Use controls for tuning, since a control patch reaches the running page without a reload. - **Editing the installed file in place also hot reloads,** through a file watcher on the user effects directory (the `path` the install returned). It skips validation, so prefer installing. A directory that did not exist when the daemon started is not watched until it restarts. ## Reading what you get back - **The capture is the canvas, not the LEDs.** It shows the composed image at full brightness before the spatial sampler maps it onto LED positions. Detail finer than the LED spacing disappears on hardware, and output brightness applies after the capture. - **`fresh: false`** (the CLI warns; REST sends `X-Canvas-Fresh: 0`) means nothing rendered during the wait, so the image is the last frame published. A burst stops at the first stale frame. - **Identical frames across a burst usually mean the script threw** inside its animation callback: one uncaught error ends the `requestAnimationFrame` chain. The console has the error. - **Console locations point at your file.** The engine loads a copy of your page with an injected preamble; messages are rewritten to the author's path and line, and each entry carries `line` and `source_file`. - **`truncated: true`** means you fell more than 512 messages behind. Do not log every frame. ## The page contract The validator rejects a page missing any of these, and lists every problem at once: a `<title>` (the catalog name), a render surface, and at least one inline `<script>`. For an LED effect the render surface is `<canvas id="exCanvas">`; the parser looks for that id, so a plain `<canvas>` fails. Keep everything inline: a rig without network access cannot fetch an outside script, and the SDK validator flags one. `references/starter.html` is a complete page that follows every rule below; copy it rather than starting blank. - **Size the canvas from the window.** The engine renders at 640x480 by default and the user can change it. Set `canvas.width = innerWidth` and `canvas.height = innerHeight`, and redo it on `resize`. A fixed `width="320" height="200"` fills only the top-left corner of the frame. - **Declare controls as meta tags.** `<meta property="speed" label="Speed" type="number" min="0" max="10" default="5">`. The everyday types are `number`, `boolean`, `color`, `combobox` (with `values="A,B,C"`), `hue`, and `text`; `sensor`, `area`, `rect`, and `asset` cover specialized inputs. `min` must be below `max`. - **Read controls every frame.** The runtime defines each control as a global before your script runs and reassigns it on every patch, so `const speed = window.speed` at load time freezes the value. Read `window.speed ?? 5` inside the frame, or define `window.onspeedChanged`, which the runtime calls after each change. Errors thrown inside an `on<name>Changed` callback are swallowed without reaching the console, so keep them trivial. - **Animate on elapsed time.** The render loop shifts between 10 and 60 fps under load; use the `requestAnimationFrame` timestamp and clamp the step. - **Audio needs an explicit opt-in:** `<meta audio-reactive="true">`, then `window.engine.audio` (`levelLinear`, `bass`, `beat`, `beatPulse`, `frequency`). The daemon does not infer it from your script. ## Designing for LEDs A gorgeous screen effect often looks like mud on a strip. Check captures against these before calling it done, and use `format: "png"` when exact colors matter. - Saturation at 85 to 100 percent, or zero for intentional white. Pastels wash out. - In vivid areas keep `min(r,g,b) / max(r,g,b)` under 0.3: at least one channel near zero. - Pure yellow reads as white; shift to amber or gold by pulling green below red. - Features should span 10 or more LEDs. Thin lines, text, fine noise, and bloom vanish after sampling. - Leave a dark floor: 30 to 50 percent of the frame dim or off reads better than everything lit. - Do not gamma correct. The engine owns the transfer curve, and a second one crushes the midtones. ## Display faces LCD faces are effects in the `display` category, rendered into `<div id="faceContainer">` rather than a canvas. `set_effect` refuses them; assign one with `set_display_face {device, effect_id}`. `capture_canvas` shows the LED scene canvas, not a face; read a face's pixels from `GET /api/v1/displays/{id}/frame`.
View on GitHub