rig
The light slice's one point of contact with the engine. `look.module` decides every number; this file stands the lights that carry them, builds the post chain once, and registers the `light` stage that pushes one frame of the arc per rendered frame.
ashbloom.light.rig
The light slice's one point of contact with the engine. look.module decides
every number; this file stands the lights that carry them, builds the post chain
once, and registers the light stage that pushes one frame of the arc per
rendered frame.
local rig = require("ashbloom.light.rig")
rig.install() -- stand the rig, register the stage (idempotent)
rig.apply(115) -- pose the look at one film time
rig.hold(115) -- freeze it there, whatever the director is doing
rig.hold(-1) -- hand the arc back to the film
rig.setRack(rows) -- layer a per-shot depth-of-field rack over this slice's
rig.report(115) -- what it holds, and the look at that moment
What stands in the scene
| entity | component | |
|---|---|---|
ab_key | Light { kind = "directional" } | the ONE shadow-casting directional light. Moon until t = 146, sun after. |
ab_fill | DirectionalLight | a cold parallel fill from the far side, no shadow |
ab_ambient | Light { kind = "ambient" } | the sky's contribution — what actually lights a crater floor at sunrise |
A second Light { kind = "directional" } would MOVE the sun rather than join it,
and would silently repaint the sky (#9076, #8139). There is exactly one, and a
second directional source is the DirectionalLight component, which registers a
distant row and is the supported path.
What is driven through a module instead of a component
The atmosphere, the fog, the exposure, the lens, the defocus, the shutter and the
debanding are pushed through their system modules, and this world carries NO
PhysicalAtmosphere / HeightFog / AutoExposure / LensFx / MotionBlur /
DepthOfField / Debanding component. A system driven from both ends has
repeatedly ended with one end inert — atmosphere.set is a silent no-op while a
PhysicalAtmosphere exists (#10301), and #10260 and #10303 are the same shape.
One authoring surface per system, and it is this one.
LensFx.tick and Exposure.tick are advanced by film.FILM_DT, not by the
frame's wall-clock delta: grain is a fresh field every frame and adaptation is a
rate, and a viewport running above 24 fps must not age the film stock faster than
the film.
The post chain
Four effects on the scene layer, so a grade never recolours a UI screen drawn
over it (#8768). Each is registered from its builtin .shader asset, which
carries its own property schema; per-frame values go through
postprocess.setProperty, and only when they have actually moved.
| priority | effect | |
|---|---|---|
| 100 | ab_bloom | soft-knee bright pass. Threshold stays UNDER 1: the scene resolves to a display-referred target, so a threshold above 1 catches nothing (#6215). |
| 180 | ab_lut | the movement's colour table, swapped on the movement boundary |
| 200 | ab_grade | the continuous palette turn — brightness, contrast, saturation, tint |
| 300 | ab_vignette |
The looks
The four tables are rebuilt from look.LUT_GRADES on every install and written
with overwrite = true — which is what makes a second install legal at all,
since without it every call after the first raises (#8774).
The hold
rig.hold(t) freezes the whole look at one film time whatever the director's
clock is doing. It is the authoring lever that lets one movement be judged on the
live viewport without the film running out from under the capture, and it is what
the carrying component's hold field drives.
Scoped to this part · feeds back into the world's score.