The second layer — the [layer] table
A preset may compose one optional second scene. The [layer] table names its
own system and carries a full authoring surface — params, easing, structural
tables — while the preset’s single [palette] colours both layers, so a world
keeps one colour language and one baked LUT (that sharing is deliberate and not
configurable; see docs/preset-palettes.md).
system = "fragment_field" # the main scene — the ground
[params]warp = "0.4"trails = "0.6" # compositing params live at the TOP level only
[layer] # the second scenesystem = "swarm" # any system — the same one twice is legaljoin = "under" # "under" (default) or "over"blend = "screen" # over-join only: add | screen | multiply | overlaymix = "0.2 + 0.8 * bass" # over-join only: bindable amount, 0..1
[layer.params] # the layer scene's own params, its namespacesize = "2.5"zoom = "0.9 + 0.3 * onset"
[layer.smoothing] # eases layer bindings — same vocabulary as [smoothing]zoom = 0.3mix = { attack = 0.05, release = 0.6 }The two joins
under— the layer draws into the same target as the main scene, before the post chain: both share trails, the kaleidoscope and bloom, and fuse into one substance. A trailed, folded ground carries the layer with it. Cost: one extra scene draw.over— the layer renders into its own offscreen and blends into the chain between the kaleidoscope and bloom: its geometry is never smeared by trails nor folded by the kaleidoscope (crisp at display resolution), but it still blooms and tonemaps with the frame — crisp and glowing. Cost: one offscreen plus one blend pass.
Know your layer’s coverage before picking under. A fullscreen scene that
presents with full coverage (a fragment field, reaction-diffusion at high
density) drawn under will occlude the main scene entirely — the layer
paints over it inside the shared target. under is the sparse-over-dense idiom
(particles, lines, an emitter over a field); a fullscreen-over-fullscreen pair
wants join = "over" with a blend mode.
The over join’s blend and mix
blend is structural (fixed at load, unknown names reject the preset):
| mode | reads as |
|---|---|
add | linear-light addition — the engine’s native idiom, the brightest |
screen | bounded brightening (the default; cannot blow out) |
multiply | darkens where the layer has coverage |
overlay | multiply below mid-grey, screen above — contrast |
Every mode operates within the layer’s own coverage: a darkening mode
darkens only where the layer actually drew. mix is the one bindable lever in
the table itself — the blend’s amount, clamped to 0..1, eased through a
[layer.smoothing] mix entry — so audio can surge the second layer in and a
preset can breathe between one world and two. mix = "0" renders exactly the
layerless preset. On an under join, blend is ignored (with a load warning)
and mix has no junction to act at.
Dark on light — the multiply route, and its two traps
multiply is how this engine draws a dark figure on a light ground, which
is worth stating plainly because the rest of the pipeline adds light and a
reader reasonably assumes dark-on-light is unavailable. The full recipe with a
worked preset is in
Colour and palettes;
the two things that will bite you belong here, beside the parameters they are
about:
- In a
multiplylayer, the darkening operand is the layer’s colour, sobrightnessruns backwards.brightness = 0is an opaque black mark; a highbrightnessclamps to white and does nothing. Nothing warns — the parameter’s meaning is inverted byblendin a different table. - The light ground has to be the chain, never
bg_*. The backdrop is composited underneath the finished chain, so no blend mode can reach it. At the defaultocclude = 1a covering layer makes the frame byte-identical to the same preset over a black backdrop (the backdrop is held out, not darkened); atocclude = 0it is added after the blend and becomes a floor the layer cannot cross: a multiply layer reaching display luma 18.9 over black reaches only 171.3 over a lit sky.
Which scene to put in the slot is a question of footprint, not of capability — both routes darken:
| layer system | coverage | gives you |
|---|---|---|
fragment_field, reaction_diffusion | every pixel (alpha = occlude) | the whole frame darkened — a wash, a gradient, a figure and its surround |
swarm, emitter | inside each mark only (alpha = the mark’s geometric falloff) | discrete dark marks on an untouched light ground |
The particle route reaches darker: a frozen swarm at brightness = 0 takes
a light chain to display luma 0.9, against 18.9 for the field route.
(A particle layer can darken, whatever an older note says: a particle’s alpha
is its geometric falloff, independent of its colour.)
What the layer does and does not get
- Its own params and easing —
[layer.params]reaches the layer’s scene only, evaluated under the same clock, analysis frame and seed salt as the top level. Per-element (index) bindings work, against the layer’s own[layer.spectrum] elements. Binding a compositing name (trails,kaleido_*,bg_*,ink_*…) inside[layer.params]warns at load and does nothing: the chain, backdrop and terminal passes belong to the preset as a whole and take their values from the top level. - Its own structural tables —
[layer.curve],[layer.generator],[layer.particles],[layer.spectrum], with the same per-system rules (a layer L-system still requires its generator table). - Its own scene instance — constructed for the preset, so the same system twice is legal (two swarms in counterpoint, two fields at different zooms) and a stateful layer (reaction-diffusion, the attractor) carries its own simulation state.
- Not its own palette (the preset’s serves both), not its own
[feedback]table (the layer gets the defaults; the attractor-as-layer’s internal trail is therefore unwarped), and no third layer — one[layer]table, total.
Heavy-plus-heavy pairings are an authoring responsibility: both tiers render
both layers, so an attractor ground under a reaction-diffusion layer costs what
the two scenes cost. Measure with --report or the diagnostics overlay before
shipping one.
Built from a8ce055 at version 0.115.0. This site tracks main and is not versioned per release.