Skip to main content

Class: LocalLights

Local light emitters: the engine-owned registry, selection, clustering, and GPU packing behind world.localLights. The game declares semantic block profiles and dynamic sources; chunk scanning, diffing, aggregation, culling, and rendering state are owned here.

A world that registers no lights and declares no profiles pays one uniform compare per fragment and nothing per frame on the CPU.

Constructors​

constructor​

• new LocalLights(options, getWorldConfig, getBlocks): LocalLights

Parameters​

NameType
optionsPartial<LocalLightsOptions>
getWorldConfig() => LocalLightsWorldConfig
getBlocks() => Iterable<EmitterBlock, any, any>

Returns​

LocalLights

Properties​

airlight​

• Readonly airlight: LocalLightAirlight

The few lights the camera sees, lighting the air (a screen effect the game adds) and the room (the chunk shader's fill). The game drives its update once per drawn frame with the camera's daylight and sight lines; an empty set costs the shaders one integer compare.


getLoadedChunk​

• getLoadedChunk: (cx: number, cz: number) => ScannableChunk

Chunk lookup for late profile changes. Populated by the world adapter; null keeps late declarations working for future loads only.

Type declaration​

▸ (cx, cz): ScannableChunk

Parameters​
NameType
cxnumber
cznumber
Returns​

ScannableChunk


grid​

• Readonly grid: LightClusterGrid


options​

• Readonly options: LocalLightsOptions


registry​

• Readonly registry: LightSourceRegistry


shadowLedger​

• Readonly shadowLedger: ShadowFrameLedger

The per-frame face-unit budget CSM and local shadows share.


shadows​

• Readonly shadows: LocalShadowScheduler

The L3 shadow tier: slot selection, cached faces, atlas renders.


stats​

• Readonly stats: LocalLightStats

Mutated in place; never reallocated.

Accessors​

blockLightOwnership​

• get blockLightOwnership(): number

Flood-ownership weight of the current tier: 1 on every tier that renders clustered lights (the analytic layer owns visible block-source lighting exclusively — nothing double-lights), 0 at off/potato (the exact legacy flood frame). This is an invariant, not configuration — read-only introspection for CPU consumers (LightShined) that mirror the chunk shader's uLocalOwnership uniform; hybrid visible stacking is not a supported state.

Returns​

number


getIsOpaqueAt​

• set getIsOpaqueAt(fn): void

Voxel opacity oracle for mount-aware shadow-face skipping, wired by the world adapter. Null disables the skip (all six faces render).

Parameters​

NameType
fn(vx: number, vy: number, vz: number) => boolean

Returns​

void


isTemporallyStable​

• get isTemporallyStable(): boolean

Returns​

boolean


uniformBindings​

• get uniformBindings(): Object

The shared uniform objects every chunk material binds. One set for the whole world; updates are zero-copy.

Returns​

Object

NameType
uAirColor{ value: Vector4[] }
uAirColor.valueVector4[]
uAirCount{ value: number = 0 }
uAirCount.valuenumber
uAirPos{ value: Vector4[] }
uAirPos.valueVector4[]
uClusteredLightCount{ value: number = 0 }
uClusteredLightCount.valuenumber
uEmissiveLevels{ value: Vector4 }
uEmissiveLevels.valueVector4
uLightData{ value: DataTexture }
uLightData.valueDataTexture
uLightGrid{ value: DataTexture }
uLightGrid.valueDataTexture
uLightGridCellSize{ value: number = 8 }
uLightGridCellSize.valuenumber
uLightGridCenter{ value: Vector4 }
uLightGridCenter.valueVector4
uLightGridDims{ value: Vector3 }
uLightGridDims.valueVector3
uLightGridHalf{ value: Vector3 }
uLightGridHalf.valueVector3
uLightGridOrigin{ value: Vector3 }
uLightGridOrigin.valueVector3
uLightGridStorageOffset{ value: Vector3 }
uLightGridStorageOffset.valueVector3
uLocalLightDebugMode{ value: number = 0 }
uLocalLightDebugMode.valuenumber
uLocalLightStable{ value: number = 1 }
uLocalLightStable.valuenumber
uLocalMaskKnee{ value: number }
uLocalMaskKnee.valuenumber
uLocalOwnership{ value: number = 1 }
uLocalOwnership.valuenumber
uLocalShadowAtlas{ value: Texture<unknown> }
uLocalShadowAtlas.valueTexture<unknown>
uLocalShadowParams{ value: Vector4 }
uLocalShadowParams.valueVector4
uLocalShadowParams2{ value: Vector4 }
uLocalShadowParams2.valueVector4
uLocalSpecularStrength{ value: number = 1 }
uLocalSpecularStrength.valuenumber
uRoomFillCoreScale{ value: number = 0 }
uRoomFillCoreScale.valuenumber
uRoomFillFloodMask{ value: number = 0 }
uRoomFillFloodMask.valuenumber
uRoomFillRangeScale{ value: number = 0 }
uRoomFillRangeScale.valuenumber
uRoomFillStrength{ value: number = 0 }
uRoomFillStrength.valuenumber

Methods​

add​

▸ add(descriptor, position): number

Parameters​

NameType
descriptorLocalLightDescriptor
positionVector3

Returns​

number


beginShadowFrame​

▸ beginShadowFrame(entities?): void

Open this frame's shadow budget and reserve units for dynamic faces (moving hero lights, entity overlays) before the CSM cascades spend.

Parameters​

NameType
entities?Object3D<Object3DEventMap>[]

Returns​

void


clearBlockProfile​

▸ clearBlockProfile(block): void

Parameters​

NameType
blockstring | number

Returns​

void


dispose​

▸ dispose(): void

Returns​

void


getDebugMode​

▸ getDebugMode(): number

Returns​

number


getQualityTier​

▸ getQualityTier(): LightQualityTier

Returns​

LightQualityTier


handleBlockUpdate​

▸ handleBlockUpdate(edit): void

A voxel changed. Only edits that touch an emitter queue a section rescan; everything else costs one AABB test per active shadow slot.

Takes the raw voxel words, not bare ids: rotating a torch in place changes only the rotation bits, yet must re-anchor its light (the scan signatures carry rotation) and refresh cached shadow maps (the stick occludes differently) exactly like swapping the block would.

Parameters​

NameTypeDescription
editObject-
edit.chunkScannableChunk-
edit.newValuenumber-
edit.oldValuenumberRaw voxel words: id in the low 16 bits, rotation/stage above.
edit.voxel[number, number, number]-

Returns​

void


handleChunkLoaded​

▸ handleChunkLoaded(cx, cz, chunk): void

A chunk's data arrived or re-arrived: queue every section for a scan.

Parameters​

NameType
cxnumber
cznumber
chunkScannableChunk

Returns​

void


handleChunkMeshed​

▸ handleChunkMeshed(cx, cz): void

A chunk mesh (re)built: refresh cached maps that reach into it.

Parameters​

NameType
cxnumber
cznumber

Returns​

void


handleChunkUnloaded​

▸ handleChunkUnloaded(cx, cz): void

A chunk left the render distance: its registrations release now.

Parameters​

NameType
cxnumber
cznumber

Returns​

void


hideDebugOverlay​

▸ hideDebugOverlay(): void

Returns​

void


invalidateShadowRegion​

▸ invalidateShadowRegion(region): void

Invalidate every cached shadow map intersecting [min, max) — for game systems that alter occluding geometry outside the block-update stream.

Parameters​

NameType
regionObject
region.maxVector3
region.minVector3

Returns​

void


onContextRestored​

▸ onContextRestored(): void

GPU context restored: CPU-side light state is authoritative, so all GPU textures simply re-upload. Wire this to the canvas's webglcontextrestored event.

Returns​

void


queryLocalLights​

▸ queryLocalLights(position, out, options?): void

CPU sample of the selected lights' combined irradiance at a point, for entities, held items, and particles. Writes into out; no allocation (per-frame callers may reuse one options scratch object).

options.floodMask is the caller's local flood-light level mapped through the mask knee (1 = fully open); masked and shadow-fallback lights multiply by it so an entity behind a wall stops tinting from a blocked light. options.timeMs drives the same flicker curve the shader evaluates.

out.claim and out.windowFade mirror the chunk shader's flood-ownership term: consumers that also apply a baked flood tint scale that tint by blockLightFloodRemainder({ scaledClaim: out.claim × {@link blockLightOwnership}, floodLevel, windowFade: out.windowFade }) so a point covered by analytic lights is never lit by both models and the window-rim crossfade matches the ground (LightShined does exactly this).

Parameters​

NameType
positionVector3
outLocalLightSample
options?Object
options.floodMask?number
options.timeMs?number

Returns​

void


rebuildProfiles​

▸ rebuildProfiles(): void

Re-resolve every block profile and rescan the loaded world through the bounded queue — for scan-time switches (BLOCK_LIGHT_TUNING's analyticHue, proxyEnergy) flipped at runtime.

Returns​

void


remove​

▸ remove(handle): boolean

Parameters​

NameType
handlenumber

Returns​

boolean


renderShadows​

▸ renderShadows(renderer, scene, entities?, instancePools?, skipShadowObjects?, poolBounds?): void

Render whatever local shadow faces this frame's remaining budget grants: moving-light refreshes and entity overlays first, then the invalidated static FIFO. A frame with zero shadow slots returns immediately.

Parameters​

NameTypeDefault value
rendererWebGLRendererundefined
sceneScene<Object3DEventMap>undefined
entities?Object3D<Object3DEventMap>[]undefined
instancePools?Group<Object3DEventMap>[]undefined
skipShadowObjectsreadonly Object3D<Object3DEventMap>[][]
poolBounds?readonly Box3[]undefined

Returns​

void


resetPeakStats​

▸ resetPeakStats(): void

Start a fresh peak-cost measurement window (benchmark harnesses).

Returns​

void


setBlockProfile​

▸ setBlockProfile(block, profile): void

Declare (or replace) the semantic light profile for a block id or name. Affects every present and future emitter of that block: all tracked sections rescan through the amortized queue.

Parameters​

NameType
blockstring | number
profileBlockLightProfile

Returns​

void


setColor​

▸ setColor(handle, color): boolean

Parameters​

NameType
handlenumber
color[number, number, number]

Returns​

boolean


setDebugMode​

▸ setDebugMode(mode): void

0 off, 1 cell occupancy heatmap, 2 isolated contribution, 3 leak mask, 4 shadow-slot tint, 5 isolated local-shadow visibility, 6 flood- ownership remainder (white = legacy flood renders, black = analytic owns).

Parameters​

NameType
mode0 | 1 | 2 | 3 | 4 | 5 | 6

Returns​

void


setDirection​

▸ setDirection(handle, direction): boolean

Parameters​

NameType
handlenumber
directionVector3

Returns​

boolean


setEnabled​

▸ setEnabled(handle, isEnabled): boolean

Parameters​

NameType
handlenumber
isEnabledboolean

Returns​

boolean


setIntensity​

▸ setIntensity(handle, intensity): boolean

Parameters​

NameType
handlenumber
intensitynumber

Returns​

boolean


setPosition​

▸ setPosition(handle, position): boolean

Parameters​

NameType
handlenumber
positionVector3

Returns​

boolean


setQualityTier​

▸ setQualityTier(tier): void

Parameters​

NameType
tierLightQualityTier

Returns​

void


setRange​

▸ setRange(handle, range): boolean

Parameters​

NameType
handlenumber
rangenumber

Returns​

boolean


setRenderPixels​

▸ setRenderPixels(pixels): void

The drawing buffer's pixel count; the world feeds it every rendered frame. Past highResolutionPixels (with hysteresis) cells keep fewer steady lights, and the ones they drop fade out.

Parameters​

NameType
pixelsnumber

Returns​

void


setTemporalStability​

▸ setTemporalStability(isStable): void

On by default. Off renders the legacy frame — camera-ranked cells, popping selection, stepped window rim, shadows that blank on every nearby remesh — and exists only for A/B captures and measurements.

Parameters​

NameType
isStableboolean

Returns​

void


showDebugOverlay​

▸ showDebugOverlay(parent): void

Wireframe bounds of every selected light, colored by state. Attached to the given parent (typically the world); allocated on first use only.

Parameters​

NameType
parentObject
parent.add(object: object) => void

Returns​

void


update​

▸ update(position): void

Per-frame work: drain the bounded scan queue, then run selection and packing (which no-op when nothing changed). Called from World.update.

Parameters​

NameType
positionVector3

Returns​

void