Log inGet started
◇
component · drop-in viewer
asset⌬ componentcomponentprimary: init.luau·originates fromworld 07158574-5…

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:

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

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:

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

FieldTypeDefaultMeaning
presetstring""Named look applied before explicit fields: clear_day, sunset, sunrise, overcast, night.
timeOfDaynumber14.00..24 hours; orients the sun and the day/night gradient.
autoCycleboolfalseAdvance timeOfDay each frame.
cycleSpeednumber60.0Game-seconds per real-second for the auto cycle.
syncSunToLightbooltrueDrive the directional light from the sun.
sunPeakIntensitynumber1.0What the sun's light measures at noon; the day/night curve runs from night up to this.
zenithColor / horizonColor / groundColorcolor—Sky gradient colours.
sunSize / sunIntensitynumber0.02 / 20.0Sun disc size + brightness.
starsIntensitynumber1.0Night star-field intensity.
moonSizenumber0.03Moon disc size.
turbiditynumber4.0Atmospheric haziness.
exposurenumber1.0Sky exposure multiplier.

Methods

MethodDescription
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).

Interface

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

conforms to

zero/source-extract/v2

ProceduralSky Component 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 exactly like a light: 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 (its own registry record over the builtin procedural-sky shader) and pushes every visual parameter (colours, sun, stars, moon, turbidity, exposure) straight to that record via `renderer.material.setProperty`. The component's fields ARE the material definition — no asset backs it. Those same values ride the native "Sky" bridge into the scene's sky configuration, alongside the time-of-day / auto-cycle / sun-sync controls that orient the directional light so the procedural day/night follows it. The configuration is what `sky.get()` reports and what a saved scene records, so it names the sky being drawn. Like the directional light, the sky is conceptually singular per scene. A ProceduralSky always FULLY defines its material on awake (every parameter from its own fields), so switching scenes never inherits a previous scene's overrides.

normalizeColor(c: { [any]: number }) → void

Normalize a colour to a `{r,g,b,a}` map in 0..1, auto-scaling a 0..255 triple. Accepts array (`{r,g,b}`) or map (`{r=,g=,b=}`) form.

argtypedescription
c{ [any]: number }

applyValues(values: { [string]: any }) → void

argtypedescription
values{ [string]: any }

currentProps( ) → void

The full property table, derived from this component's fields — the single source of truth for the runtime material's look.

registerSkyMaterial( ) → void

Register (or fully redefine — create is an upsert) the runtime sky material from the component's current fields. The point-of-use bind: the component that drives the sky owns its GPU record.

pushColor(field: string) → void

argtypedescription
fieldstring

pushScalar(field: string) → void

argtypedescription
fieldstring

currentSkyFields( ) → void

The full native "Sky" row, derived from this component's fields — the same values `currentProps` gives the GPU material, in the vocabulary the scene's sky configuration keeps them in.

pushSkyRow( ) → void

Bring the whole row to match the component. Used where every field can have moved at once — the first push, and a preset.

pushNativeColor(field: string) → void

argtypedescription
fieldstring

pushNativeScalar(field: string) → void

argtypedescription
fieldstring

takeDueSkyCapture( ) → void

Take the capture the sun's move asked for, once, on the first frame after it: by now the renderer draws the sky that sun makes, so what is captured is that sky rather than the one before it.

sunDirectionFromTime(hours: number) → void

argtypedescription
hoursnumber

lightFromElevation(elevation: number) → void

argtypedescription
elevationnumber

applySunSync(hours: number) → boolean

Orient the scene's sun-holding directional light to `hours`. Without a holder there is nothing to write: the scene's own fallback derives the sun from the sky configuration directly.

argtypedescription
hoursnumber

awake( ) → void

update(dt: number) → void

argtypedescription
dtnumber

editorUpdate( ) → void

Authoring runs with gameplay frozen, where `update` never ticks — and moving the hour to look at a sunset is exactly something done there. The capture the sun's move asked for is taken on this channel too, so the sky a scene is authored under is the sky its water reflects.

onPropertyChanged(key: ?, value: ?, _oldValue: ?) → void

argtypedescription
key?
value?
_oldValue?

onDestroy( ) → void

setTimeOfDay(hours: number) → void

Set the time of day in hours (0..24). Orients the sun and the day/night gradient.

argtypedescription
hoursnumber0..24.

examples

sky:setTimeOfDay(18.5)

applyPreset(preset: string) → void

Apply a named look (clear_day, sunset, sunrise, overcast, night).

argtypedescription
presetstringOne of the named presets.

examples

sky:applyPreset("sunset")

setZenithColor(color: { [any]: number }) → void

Set the zenith (straight-up) sky colour. Values >1 auto-scale from 0..255.

argtypedescription
color{ [any]: number }`{r, g, b}` array or `{r=, g=, b=}` map.

examples

sky:setZenithColor({0.05, 0.1, 0.3})

setHorizonColor(color: { [any]: number }) → void

Set the horizon sky colour. Values >1 auto-scale from 0..255.

argtypedescription
color{ [any]: number }`{r, g, b}` array or `{r=, g=, b=}` map.

examples

sky:setHorizonColor({0.9, 0.4, 0.2})

setGroundColor(color: { [any]: number }) → void

Set the ground (below-horizon) colour. Values >1 auto-scale from 0..255.

argtypedescription
color{ [any]: number }`{r, g, b}` array or `{r=, g=, b=}` map.

examples

sky:setGroundColor({0.3, 0.25, 0.2})

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
▣
module · born here
❒asset
# 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 ```luau 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.
▲ 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
◇
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
# 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

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.