Log inGet started
▣
module · drop-in viewer
asset⌬ modulemoduleprimary: init.luau·originates fromworld 07158574-5…

lighting

Scene-lighting base capability: ambient + directional ("sun") light and procedural-sky setup with modify-or-spawn, read-merge-write semantics, plus clear color, and the per-frame reads of the resolved lights. Identity is the component, never the entity name: the sun is the `Light…

byzero-proxy @ DESKTOP-DB3UJOJ·posted 2mo ago
What it does

lighting

Scene-lighting base capability: ambient + directional ("sun") light and procedural-sky setup with modify-or-spawn, read-merge-write semantics, plus clear color, and the per-frame reads of the resolved lights. Identity is the component, never the entity name: the sun is the Light the renderer's snapshot names as the directional holder, the ambient is the light-role component of kind "ambient", and the sky is the entity carrying a sky-role component. Name-based lookup, word resolution, and sky-material orchestration (presets, material swap) live in the lighting toolbox.

sun and ambient configuration patch the scene's resolved sun / ambient light components, spawning a carrying entity when the scene has none. A partial update (e.g. only intensity) reads the component's current fields first and merges the given fields on top, so unspecified fields keep their authored values instead of resetting to a default. sky writes the sky entity's ProceduralSky component the same way — any of its fields, plus enabled = false to remove the sky component.

Types

  • SunOpts — { direction?, color?, intensity?, castsShadows? }.
  • AmbientOpts — { color?, intensity? }.
  • ProceduralSkyOpts — ProceduralSky component fields, plus enabled = false to remove the sky.
  • SetupOpts — { sun?, ambient?, sky?, clearColor? }.
  • SetupResult — { sun?, ambient?, sky? }, the entity ids each provided section resolved to.
  • Sun — { direction, color, intensity, castsShadows, entityId? }.
  • Ambient — { color, intensity, entityId? }.
  • LightRow — one punctual light as the renderer resolved it.

Exports

Writes — each takes the opts above and answers with the entity the light resolved to:

  • M.setSun(opts: SunOpts) -> string — set the scene's sun, returning its entity id.
  • M.setAmbient(opts: AmbientOpts) -> string — set the scene's ambient term, returning its entity id.
  • M.applySetup(opts: SetupOpts) -> SetupResult — apply a lighting setup in one call. All fields optional — only provided fields change. clearColor is {r, g, b}.
  • M.setProbeVolume(key: string, volume) / M.removeProbeVolume(key: string) — publish or retract a baked irradiance probe volume.

Reads — each takes no arguments, and answers from the resolved lighting state at a cost independent of scene size:

  • M.sun() -> Sun — the sun the frame is shaded by.
  • M.ambient() -> Ambient — the ambient term the frame is shaded by.
  • M.lightRows() -> { LightRow } — every punctual light the renderer resolved this frame.

Identity:

  • M.lightKind(componentType: string, component: any?) -> string? — the kind a light-role component names.
  • M.lightOfKind(entityId: string, kind: string) -> any — the light-role component of that kind on one entity.

Usage

local lighting = require("@builtin::modules.api.engine.lighting")

lighting.setSun({ direction = { 0.3, -1, 0.2 }, intensity = 2.0 })
lighting.setAmbient({ intensity = 0.3 })
lighting.applySetup({
    sun = { direction = { 0.3, -1, 0.2 }, intensity = 2.0 },
    ambient = { intensity = 0.3 },
    sky = { timeOfDay = 14 },
})

local sun = lighting.sun()
print(sun.intensity, sun.entityId)

A reader takes no arguments and a setter takes the opts table, so lighting.sun(opts) is refused with the name of the call that applies it.

Interface

What this asset declares: the schema it conforms to, what it exposes, and the rendered structured payload.

conforms to

zero/source-extract/v2

modules/api/engine/lighting.module/init.luau Scene-lighting base capability: ambient + directional ("sun") light and procedural-sky setup with modify-or-spawn, read-merge-write semantics, plus clear color. `setSun`, `setAmbient` and `applySetup` write; `sun`, `ambient` and `lightRows` read the resolved lights every frame and each takes no arguments. Identity is the COMPONENT, never the entity name: the sun is the `Light` the renderer's snapshot names as the directional holder, the ambient is the light-role component of kind "ambient", and the sky is the entity carrying a sky-role component. Name-based lookup, word resolution, and sky-material orchestration live in the `lighting` toolbox.

LightProceduralSky

leafType(identity: string) → string

The leaf of a component identity: "@builtin::components.SpotLight" is "SpotLight", the vocabulary the kind map and every consumer speak.

argtypedescription
identitystring

declaresKind(component: any) → boolean

Whether a component declares a `kind` field of its own. Asked of the component through its field descriptors, which answer for any component — so the question is settled before the field is read, and a component that declares none is never asked for it.

argtypedescription
componentany

lightKind(componentType: string, component: any?) → string

The kind a light-role component names — `"point"`, `"spot"`, `"directional"`, `"ambient"`, `"distant"` or `"area"`. A type standing for exactly one kind spells it in its name (`SpotLight` is `"spot"`); a type covering several declares a `kind` field naming which one it is, the way `Light` does, and the field is read only from a component that declares one. So a world's own light component answers here on the same terms the engine's do. `nil` when the component names no kind.

argtypedescription
componentTypestringThe component's identity or its leaf name.
componentany?The live component, read when its type names no single kind.

examples

local kind = lighting.lightKind("SpotLight")

lightsDefaultLayer(id: string) → boolean

Whether the entity `id` lights the default render layer — the layer the scene's sun and ambient term belong to, and the one an entity with no render layer of its own lands on. A light confined to other layers reaches the cameras on those layers through its own scoped record and holds neither singleton, so the scans below pass over it. Read guarded: an entity mid-teardown answers nothing, and nothing is on no layer.

argtypedescription
idstring

lightComponentsOf(e: any) → void

Every light-role component on an entity, each paired with the type that names its kind. An entity may carry several — a spot beside the sun's `Light`, a fill `DirectionalLight` on the same holder — so the question "which light on this entity" is answered by kind rather than by taking whichever one the entity reports first.

argtypedescription
eany

lightOfKind(entityId: string, kind: string) → any

The light-role component of `kind` on one entity. An entity may carry several lights — a cone beside the sun's `Light`, a fill on the same holder — so this is how a caller reaches the one it means instead of whichever the entity reports first.

argtypedescription
entityIdstringThe entity to look on.
kindstringThe kind wanted, as `lightKind` names it.

examples

local sun = lighting.lightOfKind(holder, "directional")

lightIsOn(light: any) → boolean

Whether a light component is switched on. A switched-off component's row is out of the renderer, so it holds neither of the scene's singleton field sets whatever kind it names. `enabled` is the engine's own property on every component, so every light answers it.

argtypedescription
lightany

scanForLightKind(kind: string, exclude: string?, onlyOn: boolean?) → string

argtypedescription
kindstring
excludestring?
onlyOnboolean?

scanForHolder(kind: string, exclude: string?) → string

The first entity that can HOLD the scene's `kind` field set: a switched-on light-role component of that kind, other than `exclude`.

argtypedescription
kindstring
excludestring?

resolveSunEntity( ) → string

A singleton field set resolves from the renderer's own answer first: the snapshot names the `Light` entity the frame's sun and the frame's ambient term are each carried by, so the write lands on the light the read reports. The snapshot is a frame behind, so a light spawned THIS frame is not in it yet — the component scan covers that gap, and it is what prices the resolve by the scene rather than by its lights.

resolveAmbientEntity( ) → string

upsertLightEntity(kind: string, resolvedId: string?, label: string, fields: { [string]: any }, spawnDefaults: { [string]: any }) → string

Patch the resolved light in place, or spawn a fresh entity when the scene has none: only the provided fields change, so a partial update never resets authored values. A patched component keeps its identity, its private bake state, and its live row — replacing it would hand the scene a different light that merely looks the same. A fresh spawn starts from `spawnDefaults`; `label` names only the NEW entity, it resolves nothing. `kind` picks WHICH light on the resolved entity is patched: an entity may carry a fill or a cone alongside the one holding a singleton field set, and the patch belongs to the one whose kind the caller named.

argtypedescription
kindstring
resolvedIdstring?
labelstring
fields{ [string]: any }
spawnDefaults{ [string]: any }

resolveSkyEntity( ) → string

The scene's sky entity — the one carrying a sky-role component (ProceduralSky / Skybox). Identity by component, same as the lights.

applySetup(opts: SetupOpts) → SetupResult

Apply a lighting setup in one call. All fields optional — only provided fields change (read-merge-write; unspecified fields keep their authored values). `sun` and `ambient` patch the scene's resolved sun / ambient light components, spawning a carrying entity when the scene has none. `sky` writes the sky entity's ProceduralSky component the same way — any of its fields, plus `enabled = false` to remove the sky component. `clearColor` is `{r, g, b}`. Returns the entity ids each provided section resolved to.

argtypedescription
optsSetupOpts

setSun(opts: SunOpts) → string

Set the scene's sun, the same opts `applySetup`'s `sun` section takes. Read-merge-write: only the fields given change, so `{ intensity = 2 }` keeps the authored direction and colour. The light is resolved by component, and a scene carrying none is given one. castsShadows?: boolean }`.

argtypedescription
optsSunOpts`{ direction?: {x,y,z}, color?: {r,g,b}, intensity?: number,

examples

lighting.setSun({ intensity = 2.4, color = { 1, 0.95, 0.85 } })

setAmbient(opts: AmbientOpts) → string

Set the scene's ambient term, the same opts `applySetup`'s `ambient` section takes. Read-merge-write: only the fields given change, so `{ intensity = 0.5 }` keeps the authored colour. The light is resolved by component, and a scene carrying none is given one.

argtypedescription
optsAmbientOpts`{ color?: {r,g,b}, intensity?: number }`.

examples

lighting.setAmbient({ intensity = 0.5 })

lightScans( ) → number

How many times, since the module loaded, a light was reached by walking the scene's entities rather than through the record its own component published. A write that finds its light through the record leaves this count where it was, whatever the scene holds; one that walks the scene raises it by one per walk. Read it either side of a write to know which the write was.

examples

local before = lighting.lightScans(); lighting.setAmbient({ intensity = 0.5 }); assert(lighting.lightScans() == before)

__registerSun(record: Sun?) → void

Publish the scene's sun — called by the `Light` component whenever the directional light it carries wakes, changes, or goes away. Nothing else should call this: the component is the one that knows.

argtypedescription
recordSun?The sun's current values, or nil when the scene has no sun.

__lightsDefaultLayer(id: string) → boolean

Whether the entity `id` lights the default render layer — the layer the scene's sun and ambient term belong to. A `Light` confined to other layers reaches the cameras on those layers through its scoped record and holds neither singleton, so it asks here before publishing itself as the sun.

argtypedescription
idstringThe entity carrying the light.

__resolveSun(exclude: string?) → void

Find a directional light in the scene and publish it as the sun. Called by a `Light` component that is giving the sun up — despawned, switched off, or no longer directional. A scene may carry several directional lights, so which one holds the sun is a question about the SCENE, not about the light that is leaving: the departing one cannot know who should take it. Only a switched-on light can take it, the renderer holding no row for one that is off. This scans, which is why it runs only on that handoff and never per frame. does so and is therefore skipped.

argtypedescription
excludestring?The light handing the sun on, which is still in the scene as it

sun( ) → Sun

The scene's sun: the values its directional `Light` currently carries. A table read when a component holds the sun — safe to call every frame.

examples

local s = lighting.sun(); print(s.intensity, s.direction[2])

directionals( ) →

Every directional light with its render layer scope: one record per light entity, each carrying `entityId`, `layerMask` (the render layer mask of the entity carrying it — cameras whose include mask intersects it are lit by it), `color`, `intensity`, `direction` and `castsShadows`. A light on an entity with no explicit render layer records the default layer.

examples

for _, d in lighting.directionals() do print(d.entityId, d.layerMask) end

ambients( ) →

Every ambient light with its render layer scope, on the same terms as `directionals`: one record per light entity, each carrying `entityId`, `layerMask`, `color` and `intensity`.

examples

for _, a in lighting.ambients() do print(a.entityId, a.layerMask) end

ambient( ) → Ambient

The scene's ambient term: the colour and intensity the frame's ambient light is shaded by, as the renderer resolved it. The term is one field set with one holder, so `entityId` names the ambient `Light` whose values it carries — a scene carrying several ambient lights reads here which of them the frame is shaded by, and the rest are authored and unread until one of them takes the term. ambient lights the scene.

examples

local a = lighting.ambient(); print(a.intensity, a.entityId)

setProbeVolume(key: string, volume: { [string]: any }) → void

Publish (or replace) the irradiance light-probe volume under `key`: `volume` is { boundsMin = {x,y,z}, boundsMax = {x,y,z}, res = {x,y,z}, sh = {...} } where `sh` is the baked SH L2 field as a flat float array (9 vec4 = 36 floats per probe, X-fastest then Y then Z). A scene carries one entry per baked volume (adaptive brick set); every standard-PBR fragment inside a volume's bounds takes its ambient term from the covering field(s) instead of the flat ambient light. `VolumeProbe.bake` publishes each freshly baked field through here automatically.

argtypedescription
keystring
volume{ [string]: any }

lightRows( ) → void

Every punctual light in the scene as the renderer resolved it this frame: world-space `position`, `direction`, `color`, `intensity`, `radius`, the spot cone as `coneInnerCos`/`coneOuterCos`, and an area light's rect as `tangentU`/`tangentV` with `halfWidth`/`halfHeight` and `twoSided`. `kind` is "point", "spot", "area", or "distant", and `entityId` names the carrying entity when one exists. Every vector is a `{x, y, z}` array. A "distant" row is parallel light arriving from `direction` everywhere in the world — a second star, a moon, a fill from the far side. Its `direction`, `color` and `intensity` are what the shading reads, and its `radius` is 0: distance does not attenuate parallel light. The scene's sun is a single field rather than a row: read it with `lighting.sun()`. The transform hierarchy is already applied, so anything that has to shade the same lights the renderer does — a GI bake, a lighting inspector — reads world space here instead of re-deriving it from components. `shadowSlot` is where a shadow-casting point light was seated in the point-shadow cube pool, and `-1` when it was not: the pool holds `renderer.pointShadowBudget().slots` lights, and a caster past it renders lit with no shadow. `shadowLayer` and `shadowResolution` say the same for a spot or a rect: the spot-shadow atlas layer its depth map went into and the texel resolution it was given there, with `-1` for a caster the atlas had no tile left for.

removeProbeVolume(key: string) → void

Retract the irradiance light-probe volume published under `key` (volume removed / bake cleared). An empty key clears every published volume; fragments outside every remaining volume fall back to the flat ambient light term.

argtypedescription
keystring
⌬ Types
LightRow = {Sun = {Ambient = {ScopedDirectional = {ScopedAmbient = {SunOpts = { direction: { number }?, color: { number }?, intensity: number?, castsShadows: boolean? }AmbientOpts = { color: { number }?, intensity: number? }ProceduralSkyOpts = { [string]: any }SetupOpts = {SetupResult = {

Sub-parts

Everything contained inside this part. Assets are composite children (clickable cards). Files are leaf payloads. Expand any row to view its source.

20items
◇
component · born here
❒asset
# Light Adds a light source to an entity. The entity's world transform determines the light's position (point) or direction (directional). Point light positions are auto-synced from that world transform, so a light on a child entity burns where the entity stands — the position `entity.position` reports. A point light's `intensity` is on one scale with the `SpotLight` component's `intensity`/`brightness`: the same number at the same `radius`/`range` puts the same light on a surface either kind faces from the same place, and a spot spends it on the cone it opens on rather than all around itself. Kinds: `"point"`, `"spot"`, `"directional"` (the scene's sun), `"ambient"`, `"distant"` (a parallel light beside the sun). Public fields: `kind`, `colorR/G/B`, `intensity`, `radius`, `directionX/Y/Z`, `castsShadows`, `lightChannels`, `mobility`. `range`, `color` and `direction` are aliases accepting the composite/renamed forms. Methods: `:setColor(color)` (color: `{r, g, b}` array or `{r=, g=, b=}` map), `:setIntensity(i)`, `:setRadius(r)`, `:setDirection(dir)`, `:setKind(lightKind)`. `kind = "spot"` opens a cone along the entity's forward axis, at the engine's default 30° outer and 20° inner half-angles; the `SpotLight` component is the one that carries the cone angles and the `face` axis as fields. `"directional"` and `"distant"` are parallel lights: they arrive from the same direction at every point in the world and no distance attenuates them, so their `intensity` reads against the sun's rather than against a point light's. The `SpotLight` README carries the rest of how to balance a mixed point-and-spot rig. ```luau entity(id).component.add("Light", { kind = "point", intensity = 2, radius = 10 }) entity(id).component.add("Light", { kind = "directional", direction = {-0.5, -1, -0.3} }) ```
▲ 0↑ born
◇
component · born here
❒asset
# ProceduralSky The editable atmospheric sky: a day/night gradient with a sun disc, glow, stars and a moon, all derived procedurally from the directional light's elevation. Add it to an entity like a light: ```lua entity.spawn("sky").component.add("ProceduralSky", { timeOfDay = 18.5 }) entity.spawn("sky").component.add("ProceduralSky", { preset = "sunset" }) entity.spawn("sky").component.add("ProceduralSky", { zenithColor = { r = 0.05, g = 0.1, b = 0.3 }, horizonColor = { r = 0.9, g = 0.4, b = 0.2 }, }) ``` A `ProceduralSky` owns its material outright: it registers a **runtime GPU material** of its own over the builtin procedural-sky shader and pushes every visual parameter (colours, sun, stars, moon, turbidity, exposure) straight to that record. The component's fields **are** the material definition — no `.material` asset backs it, so retuning a scene's sky changes that scene's sky and nothing else. The renderer reads the values from the material's group(1) uniform like any other material — there is no sky-specific render path. Those values also travel the native `Sky` bridge into the scene sky config, alongside the time-of-day / auto-cycle / sun-sync controls. That config is what `sky.get()` reports and what a saved scene records, so both name the sky being drawn. With `syncSunToLight` on (the default), the sky's `timeOfDay` is what the scene is lit by: the scene's directional light takes its direction, colour and intensity from the sun's position, so the sun in the sky and the sun the scene is lit by are the same sun through a whole day/night cycle. Turn it off for a light the sky leaves alone. The intensity that arrives is the day/night curve between night and noon, and `sunPeakIntensity` is the noon end of it — `1.0`, the sky's own daylight, unless a scene asks for more. A harder sun on water or snow is stated here; setting the light itself does not survive, because the sky writes over it every time the hour moves. Like the directional light, the sky is conceptually **singular per scene**. A `ProceduralSky` fully defines its material on `awake` (every parameter from its own fields), so switching scenes never inherits a previous scene's overrides. Day/night comes from the sun's elevation, so evening presets render dark — drive the look by `timeOfDay` (which orients the sun). ## Fields | Field | Type | Default | Meaning | |---|---|---|---| | `preset` | string | `""` | Named look applied before explicit fields: `clear_day`, `sunset`, `sunrise`, `overcast`, `night`. | | `timeOfDay` | number | `14.0` | 0..24 hours; orients the sun and the day/night gradient. | | `autoCycle` | bool | `false` | Advance `timeOfDay` each frame. | | `cycleSpeed` | number | `60.0` | Game-seconds per real-second for the auto cycle. | | `syncSunToLight` | bool | `true` | Drive the directional light from the sun. | | `sunPeakIntensity` | number | `1.0` | What the sun's light measures at noon; the day/night curve runs from night up to this. | | `zenithColor` / `horizonColor` / `groundColor` | color | — | Sky gradient colours. | | `sunSize` / `sunIntensity` | number | `0.02` / `20.0` | Sun disc size + brightness. | | `starsIntensity` | number | `1.0` | Night star-field intensity. | | `moonSize` | number | `0.03` | Moon disc size. | | `turbidity` | number | `4.0` | Atmospheric haziness. | | `exposure` | number | `1.0` | Sky exposure multiplier. | ## Methods | Method | Description | |---|---| | `sky:setTimeOfDay(hours)` | Set the time of day (0..24). | | `sky:applyPreset(name)` | Apply a named look. | | `sky:setZenithColor(c)` / `setHorizonColor(c)` / `setGroundColor(c)` | Set a gradient colour (`{r,g,b}` array or map; >1 auto-scales /255). |
▲ 0↑ born
◇
component · born here
❒asset
# Skybox The scene's sky as a single **material**. Add it to an entity and the engine renders that material across the sky behind everything else: ```lua entity.spawn("sky").component.add("Skybox", { material = "sky_cubemap" }) entity.spawn("sky").component.add("Skybox", { material = "my_custom_sky" }) entity.spawn("sky").component.add("Skybox", { kind = "none" }) -- explicit no sky ``` `Skybox` is the generic, material-driven sky: it points at **any** sky material — a builtin (`sky_procedural`, `sky_solid`, `sky_cubemap`, `sky_equirect`) or your own authored sky-domain material — and makes it the active sky. For the editable procedural atmosphere (day/night, sun, stars, moon), use the [`ProceduralSky`](../ProceduralSky.component/README.md) component instead — it is a `Skybox` over the `sky_procedural` material plus typed parameter controls. A Skybox **materialises its material's GPU handle** when it binds it. The sky's point-of-use is the sky pass, which never binds the material the way a `Model` does, so the component performs the Disk→CPU→GPU upload (`:handle()`) itself. The renderer binds the sky by the material's **identity** (the key it is resident under once materialised), so a material-mode sky renders correctly after boot and mode flips — no manual `:handle()` needed. Like the directional light, the sky is conceptually **singular per scene**: the engine copies the most recently authored sky into the scene-wide sky config the renderer reads each frame. A sky belongs to the scene that spawned it; removing the component reverts the scene to the engine fallback sky. State is bridged through the native `Sky` ECS component (sky type `material` / `none`), so there is no global sky singleton — two scenes can never clobber each other. ## Fields | Field | Type | Default | Meaning | |---|---|---|---| | `material` | string | `sky_procedural` | The sky material to render. Any registered material whose shader is a sky-domain shader. Defaults to the procedural sky so an empty `Skybox{}` is never a black void. | | `kind` | string | `material` | `"material"` renders `material`; `"none"` turns the sky pass off — the explicit authored form of "this scene has no sky". | ## Methods | Method | Description | |---|---| | `skybox:setMaterial(name)` | Point the sky at a different material (materialises its handle). | | `skybox:setNone()` | Turn the sky off explicitly. |
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born
▣
module · born here
❒asset
# environment Module Environment / reflection capture — bake the scene into reflection-probe cube slots from world positions, persist them as `faces6` `.texture` assets, set per-probe blend data so surfaces reflect the probes covering them, and capture the sky into its own slot as the fallback under them. Public Luau surface over the `__environment` Internal FFI namespace, auto-injected as `_G.environment` via the prelude. ## Purpose The generic "render the scene into a cubemap from a point" capability the reflection-probe system is built on. Captures are queued for the render system (which owns the live scene); `captureSlotToAsset` additionally yields a few frames while the GPU readback completes. Persisted cubes are `faces6` `.texture` assets (px/nx/py/ny/pz/nz PNGs + a `cube.yaml` sidecar — see `docs/specs/cubemap-textures.md` §4 for the face convention). For probe authoring use the higher-level `reflectionProbe` module; reach for `environment` when you need the raw per-slot primitives. ## Usage ```luau -- Register probe blend data: index i maps to cube slot i. environment.setProbes({ { x = 0, y = 2, z = 0, radius = 12 } }) -- Bake slot 0 from a point (queued, next frame). environment.captureSlot(0, 0, 2, 0) -- Bake + persist to /source/probe_lobby.texture/ (yields; call from a -- task/coroutine/execute context). local path, err = environment.captureSlotToAsset("probe_lobby", 0, 0, 2, 0) -- Restore a persisted cube into a slot WITHOUT re-rendering. environment.loadSlotFromAsset("probe_lobby", 0) -- Capture the sky alone into the fallback slot: a surface no probe covers -- reflects the sky rather than black. environment.captureSky() ``` ## Exports - `environment.setProbes(probes) -> boolean` — set active probes' blend data; array of `{ x, y, z, radius, priority? }`, index i → cube slot i, gathered highest `priority` first - `environment.captureSky(x?, y?, z?) -> boolean` — render the sky alone into the fallback slot and arm it (queued) - `environment.setSkyFallback(active) -> boolean` — arm/disarm the fallback against the sky already captured (arming is refused while the slot holds none) - `environment.captureSlot(slot, x, y, z) -> boolean` — bake the scene into a slot from a point (queued) - `environment.captureSlotToAsset(name, slot, x, y, z, timeoutFrames?) -> (string?, string?)` — bake + persist as a `faces6` `.texture`; yields - `environment.loadSlotFromAsset(name, slot) -> (boolean, string?)` — upload a persisted cube into a slot without re-rendering Back-compat single-global-reflection helpers (slot 0 + one full-coverage probe): - `environment.capture(x, y, z) -> boolean` - `environment.captureToAsset(name, x, y, z) -> (string?, string?)` - `environment.loadFromAsset(name) -> (boolean, string?)`
▲ 0↑ born
✦
shader · born here
❒asset
# procedural_sky The editable atmospheric sky: a day/night gradient with a sun disc and glow, a procedural star field, and a moon opposite the sun, authored entirely from material properties — no texture backs it. Day, night and the dawn/dusk blend all derive from the directional light's elevation, so the sky follows whatever drives the scene's sun; there is no time-of-day uniform. The gradient's zenith, horizon and ground colours, the sun's size and intensity, the stars, the moon, turbidity and exposure come from `properties.yaml`. Output is radiance scaled by `exposure` — the scene's own tone-mapping does the range compression, the same contract as every sky-domain shader. The `ProceduralSky` component owns the runtime material over this shader (`__procedural_sky`) and pushes its fields here; `sky.get()` reports the resulting configuration.
▲ 0↑ born
·
other · born here
▤file
▲ 0↑ born

Problems

Everything affecting this asset right now: its own problems, anything wrong inside it, and problems on its direct dependencies.

0problems
No problems reported. This asset, its contents, and its direct deps are clean as of the latest commit.
⌬ZeroMind agent review · awaiting first pass
Findings
Reviewer findings (handle · model · tag · quoted note) appear here once the per-pass review log lands. Today only the rolled-up agent_score is exposed.
usability—
did it work as advertised
quality—
authoring polish + cohesion
performance—
frame & memory budget held
agent review score
—
/ 100
awaiting first pass
usability × 0.40
+ quality × 0.35
+ performance × 0.25
± compat factor

Usability ratings

Did the part work as advertised when consumers tried to drop it in. Separate from upvotes: those are taste; this is "did it function".

—%no reports yet
Sign in to report whether this part worked for you.
Discussion

Scoped to this part · feeds back into the world's score.

0comments
Sign in to post.sign in
No comments yet. Be the first.