Builtin Library
README.md
reflectionProbe Module
Reflection-probe system — multiple proximity-blended reflection probes in a scene. Each probe bakes the scene into its own cube slot from its position; surfaces reflect the probes covering them, blended by proximity and rank (the renderer's per-fragment probe blend), with the scene's sky under whatever coverage the probes leave. Auto-injected as _G.reflectionProbe via the prelude.
Purpose
Policy lives here: slot assignment (max 8 cube slots) and the active-probe list pushed to the renderer. The engine only provides the generic per-slot capture + the proximity-blend buffer (via the environment global). The ReflectionProbe component drives this registry from its lifecycle; agents/editors can also drive it directly (add / bake / bakeAll / list).
Rank and the sky under it
Probes are gathered one rank at a time, highest priority first, and each rank takes the coverage the ranks above it left. A small interior probe ranked above the large exterior one it sits inside therefore wins outright wherever it reaches full weight, while probes of equal rank crossfade by proximity. Coverage no probe claims reflects the scene's sky, captured into its own slot by ensureSkyFallback() (which bakeAll runs, and the ReflectionProbe component runs once its probe holds a cube), so a reflective surface outside every probe's radius shows the sky rather than black. The capture follows the sky from there: change the sky and the slot is retaken from where it was captured.
A probe reaches surfaces only once it holds a cube, baked by bake / bakeAll or loaded from its persisted bake by loadBaked. An unbaked probe keeps its slot and is listed with baked = false, and adds nothing to the frame; the ReflectionProbe component arms its sky fallback once the probe holds a cube. lighting.get reports the same state in its environment block, beside whether the sky fallback is armed, so a surface that stays lit with every light at zero names what it is reflecting.
Each probe persists its baked cube at probe_<key>.texture (key = the probe's probeId, falling back to the entity id) and loads that asset at runtime instead of re-baking — baking is an authoring step.
Usage
-- One-liner authoring: spawn a probe entity (does NOT bake on add).
local id = reflectionProbe.add(0, 3, 0, { radius = 15, probeId = "lobby" })
-- One-call bake: bake EVERY registered probe once the scene is built.
-- Yields a few frames (geometry must be GPU-resident) — call from a
-- task/coroutine/execute context.
reflectionProbe.bakeAll()
-- Inspect / tweak
for _, p in ipairs(reflectionProbe.list()) do print(p.id, p.slot, p.radius, p.baked) end
reflectionProbe.setRadius(id, 20)
reflectionProbe.setPriority(id, 1) -- outranks the probes it overlaps
reflectionProbe.apply() -- re-push live positions/radii after moving a probe
Exports
reflectionProbe.add(x, y, z, opts?) -> string— spawn a probe entity (opts{ radius = 12, probeId = "stable-key", name }); returns the entity idreflectionProbe.bakeAll() -> { baked, failed, errors }— bake + persist every registered probereflectionProbe.bake(id) -> (string?, string?)— bake one probe from its position + persist; returns the asset path or(nil, err)reflectionProbe.loadBaked(id) -> boolean— load the persisted baked cube into the probe's slot (the runtime path; false when none exists)reflectionProbe.register(id, radius, key?) -> number?— assign a free cube slot (idempotent; called by the component's awake)reflectionProbe.unregister(id)— free the probe's slotreflectionProbe.setRadius(id, radius)— update influence radiusreflectionProbe.setPriority(id, priority)— set the probe's blend rank against the probes it overlaps (default 0)reflectionProbe.ensureSkyFallback() -> boolean— put the scene's sky in the fallback slot: capture it when the slot holds none, re-arm it when it doesreflectionProbe.apply() -> number— push the current active-probe blend data to the rendererreflectionProbe.isBaked(id) -> boolean- whether the probe holds a cube it reflectsreflectionProbe.baking(id?) -> boolean- whether a bake under way will reach probeid(its ownbake, or abakeAllwith it still to reach); with noid, whether any bake is under wayreflectionProbe.list() -> { { id, slot, radius, priority, baked, asset, position } }reflectionProbe.count() -> number