---
name: spiral-scene
description: Set up a Spiralyst Lab scene programmatically — choose a fractal type + params, effects (trails/glow), layers, background, and wire sliders to audio reactivity or sine animation. Invoke when the user says /spiral-scene or asks to configure/build a specific look (e.g. "six-petal rose with trails, hue reacting to the kick, rotating slowly").
---

# Spiralyst Lab — set up a scene

Drive the app over its local HTTP API. Read `$BASE` and `$AUTH` from
`~/.spiralyst-lab/api-port.json` as in `/spiral-observe`, and send the bearer
token on every request.

## The model (read the live schema, don't guess)

The control surface is self-describing — prefer it over guessing:

- `GET /api/state` — current full scene; mutate a copy and POST it back.
- `POST /api/state/apply` (body = full state) — applies via the same snapshot /
  restore path the UI uses (~250 ms; rebuilds controls for the type).

A scene = these fields on the state object:

- `type` — fractal id (e.g. `fractal-rose`, `fractal-phyllotaxis`, `mandelbulb`,
  `svg-fractal`). `params` — the per-type params (e.g. rose: `{k, shrink, rotInc}`).
- `effects` — `{glow, trails, vignette}` (trails 0..0.95 = persistence).
- `layers` — `{count, hueOffset, opacity, centerOffset}`.
- `color` — `{hueStart, hueSpan, sat, light, ramp}`.
  `bgGradient` — `{enabled, endColor, angle}`.
- `audio` — set `{enabled: true, source: "system"}` so the tap feeds reactivity.
- `sliders` — per-control animator config keyed `"group:key"`:
  `{min, max, mode, audioBand, freq, muted, soloed, inverse}` where:
  - `mode` ∈ `off | sine | audio`
  - `audioBand` = `{lo, hi, signal, gain, noiseFloor, presetName}`;
    `signal` ∈ `amplitude | flux | centroid | pitch | pitchVelocity`;
    band presets: sub 20-60, kick 40-100, bass 60-250, snare 180-500, mid 250-2000,
    vocal 200-3500, lead 800-5000, high 4k-22k, hat 6k-14k, air 12k-22k.
  - `sine` uses `freq` (default 0.15).

  **`noiseFloor` is a squelch** (byte-FFT magnitude 0-240, default 16): raise it so
  a steady band's constant floor gates to 0 and only peaks register — the key lever
  for making a flat band punchy. Tune gain + squelch + signal together, not gain
  alone (or just run `/spiral-heal` to do it automatically).

## What to do when invoked

1. `GET /api/state` into a working copy.
2. Set `type` + `params`, `effects`, `layers`, `color` / `bgGradient` per the ask.
3. For reactive controls: set `sliders["group:key"].mode = "audio"` + an `audioBand`.
   For motion: `mode = "sine"` + a `freq` (half the default = 0.075).
4. Enable audio (`audio.enabled = true, source: "system"`) if reactivity is wanted.
5. `POST /api/state/apply`. Wait ~1.5 s.
6. **Verify**: `GET /api/state` (modes landed) and export a PNG via
   `/api/export/png` to read the result. If a reactive band pins at its max,
   run `/spiral-heal`.

## Worked example — six-petal rose, kick-reactive hue, slow spin

```
type: "fractal-rose"
params: {k: 6, shrink: 0.36, rotInc: 0.3}
effects.trails: 0.7
audio: {enabled: true, source: "system"}
sliders["color:hueStart"]: {
  min: 0.55, max: 1.0, mode: "audio",
  audioBand: {lo: 40, hi: 100, presetName: "kick", signal: "flux", gain: 1.0}
}
sliders["transform:rotation"]: {min: 0, max: 6.283, mode: "sine", freq: 0.075}
```

(Use `flux` not `amplitude` for beat response, then `/spiral-heal` to tune gain.)
