luv

Workshop wiki

light.lisp

luvcraft/light.lisp

system luvcraft/core · 65 definitions · on GitHub

The voxel light field: a derived domain beside block content.

Each resident chunk may own a chunk-light-field: dense sky and blocklight levels with their own revision and boundary revisions, so relighting never impersonates a content edit. The semantic objects live at the chunk boundary; the 4096-site columns inside are plain (unsigned-byte 8) arrays, and the solver dispatches block light behavior once per palette entry rather than per cell.

This file owns capture, publication, and the closed production solver protocol. The compiled frontier programs live in FRONTIER-LIGHT.LISP; the deliberately simple differential oracle is loaded only by the LUVCRAFT/LIGHT-REFERENCE system.

in-package#:luvcraft
records:define-columnar-materializationvoxel-light-columns
sky-level0:type
unsigned-byte8
block-level0:type
unsigned-byte8
defmethodfields:field-representation-domain
voxel-light-columns-domaincolumns
defclasschunk-light-field
columns:initarg:columns:readerchunk-light-field-columns
revision:initform0:accessorchunk-light-field-revision

-X, +X, -Y, +Y, -Z, +Z, indexed like content boundary revisions.

boundary-revisions:initform
make-array6:element-type'
unsigned-byte64
:initial-element0
:readerchunk-light-field-boundary-revisions
state:initform:unlit:accessorchunk-light-field-state
:documentation

One chunk's derived sky and blocklight levels, revised independently from block content. STATE is :UNLIT before any solve, :STABLE when every boundary was known, and :PROVISIONAL when an unknown residency boundary contributed to the result.

defunchunk-light-field-sky-definition
field
records:columnar-row-lane-declaration
voxel-light-columns-row-declaration
chunk-light-field-columnsfield
'sky-level
defunchunk-light-field-block-definition
field
records:columnar-row-lane-declaration
voxel-light-columns-row-declaration
chunk-light-field-columnsfield
'block-level
defunchunk-light-field-sky-levels
field
voxel-light-columns-sky-level-lane
chunk-light-field-columnsfield
defunchunk-light-field-block-levels
field
voxel-light-columns-block-level-lane
chunk-light-field-columnsfield
defmethodfields:materialized-field-representation
field-name
eql:sky-light
declare
ignorefield-name
chunk-light-field-columnsfield
defmethodfields:materialized-field-representation
field-name
eql:block-light
declare
ignorefield-name
chunk-light-field-columnsfield
defunmake-chunk-light-field
domain

Make an unlit resident field whose columns are bound to domain.

make-instance'chunk-light-field:columns
make-voxel-light-columnsdomain:declarations`
sky-level.,
block-level.,
defunchunk-light-field-boundary-revision
fielddirection
aref
chunk-light-field-boundary-revisionsfield

What a missing resident neighbor means is a world/source decision. The solver never equates :ABSENT with open sky on its own.

defgenericabsent-chunk-light-semantics
sourceworldchunk-keydirection
:documentation

How light should treat the absent neighbor of chunk-key in direction.

Return :OPEN-SKY for a boundary known to see the sky, :CLOSED for a boundary known to be outside the world, or :UNKNOWN for terrain which merely is not resident.

defmethodabsent-chunk-light-semantics
sourcet
worldchunk-keydirection
declare
ignoreworldchunk-keydirection
:unknown
defmethodabsent-chunk-light-semantics
worldchunk-keydirection

The little generated world declares open sky above its known vertical extent and a closed floor beneath it. Lateral terrain is generatable but simply not resident, which is exactly :UNKNOWN.

declare
ignoreworldchunk-key
cond
plusp
voxel-direction-dydirection
:open-sky
minusp
voxel-direction-dydirection
:closed
t:unknown

Palette-indexed light tables: one generic dispatch per palette entry, then dense u8 lookups in every hot loop.

defclassblock-palette-domain
palette:initarg:palette:readerblock-palette-domain-palette
:documentation

The finite block-state sites addressed by palette code.

defmethoddomains:domain-cardinality
length
block-palette-domain-palettedomain
records:define-columnar-materializationblock-light-properties
propagation-loss0:type
unsigned-byte8
emission-level0:type
unsigned-byte8
defmethodfields:field-representation-domain
block-light-properties-domainproperties
defunrepresented-block-slot-declaration
slot-name

Project block-kind's semantic slot metadata into a storage declaration.

defunblock-palette-light-properties

Materialize propagation loss and emission over palette's entry domain.

let*
domain
make-instance'block-palette-domain:palettepalette
properties
make-block-light-propertiesdomain:declarations`
propagation-loss.,
emission-level.,
records:with-columnar-materialization-storage
borrowed-domainextentrow
lossespropagation-loss
emissionsemission-level
propertiesblock-light-properties
declare
ignorerow
assert
eqdomainborrowed-domain
dotimes
indexextent
properties

A captured region: every resident chunk's dense content beside fresh work arrays. The reference solver reads and writes only this capture, then publishes complete fields in one pass.

defstruct
light-region-entry
:constructor%make-light-region-entry
chunknil
keynil:type
ornullchunk-coordinate
content-definitionnil
indicesnil:type
ornull
simple-array
unsigned-byte16
block-propertiesnil:type
light-columnsnil:type
defunlight-region-entry-domain
entry
voxel-light-columns-domain
light-region-entry-light-columnsentry
defunlight-region-entry-sky-definition
entry
records:columnar-row-lane-declaration
voxel-light-columns-row-declaration
light-region-entry-light-columnsentry
'sky-level
defunlight-region-entry-block-definition
entry
records:columnar-row-lane-declaration
voxel-light-columns-row-declaration
light-region-entry-light-columnsentry
'block-level
defunlight-region-entry-opacity-lut
entry
block-light-properties-propagation-loss-lane
light-region-entry-block-propertiesentry
defunlight-region-entry-emission-lut
entry
block-light-properties-emission-level-lane
light-region-entry-block-propertiesentry
defunlight-region-entry-sky
entry
voxel-light-columns-sky-level-lane
light-region-entry-light-columnsentry
defunlight-region-entry-block
entry
voxel-light-columns-block-level-lane
light-region-entry-light-columnsentry
defmethodluvcraft.world.fields:materialized-field-definition
entrylight-region-entry
field-name
eql:block-content
declare
ignorefield-name
light-region-entry-content-definitionentry
defmethodluvcraft.world.fields:materialized-field-definition
entrylight-region-entry
field-name
eql:sky-light
declare
ignorefield-name
defmethodluvcraft.world.fields:materialized-field-definition
entrylight-region-entry
field-name
eql:block-light
declare
ignorefield-name
defmethodfields:materialized-field-representation
entrylight-region-entry
field-name
eql:sky-light
declare
ignorefield-name
light-region-entry-light-columnsentry
defmethodfields:materialized-field-representation
entrylight-region-entry
field-name
eql:block-light
declare
ignorefield-name
light-region-entry-light-columnsentry
defstruct
light-region
:constructor%make-light-region
worldnil
spacenil:type
entries
make-hash-table:test#'equalp
:typehash-table

Frozen regions carry the source's answer for every absent boundary so a producer never consults the live world while solving a captured batch.

absent-semanticsnil:type
ornullhash-table

A from-scratch capture enumerates its entries eagerly. An incremental candidate instead materializes entries on first touch, initialized from the chunk's current published field, so propagation may wander into any resident chunk without precomputing the affected set.

ensure-entrynil:type
defunadd-light-region-entry
regionchunk&keyfrom-field-pcopy-content-p

Materialize chunk's dense capture in region and return the new entry.

with-block-content-storage
domainpaletteindices
chunk
let*
key
chunk-domain-coordinatedomain
field
andfrom-field-p
block-chunk-light-fieldchunk
sky-definition
block-definition
light-columns
make-voxel-light-columnsdomain:declarations`
sky-level.,sky-definition
block-level.,block-definition
sky
voxel-light-columns-sky-level-lanelight-columns
block-levels
voxel-light-columns-block-level-lanelight-columns
whenfield
replaceblock-levels
setf
gethashkey
light-region-entriesregion
%make-light-region-entry:chunkchunk:keykey:content-definition
luvcraft.world.fields:materialized-field-definition
block-chunk-contentchunk
:block-content
:indices
coerce
ifcopy-content-p
copy-seqindices
indices
'
simple-array
unsigned-byte16
:block-properties:light-columnslight-columns
defunlight-region-boundary-key
keydirection
listkey
voxel-direction-dxdirection
voxel-direction-dydirection
voxel-direction-dzdirection
defuncapture-light-region
world&keyimmutable-p

Capture every resident chunk of world for a from-scratch relight.

With immutable-p, copy content indices and capture absent-boundary semantics; the returned region may then be solved without reading the live world.

let
region
%make-light-region:worldworld:space
block-world-spaceworld
:absent-semantics
andimmutable-p
make-hash-table:test#'equalp
dolist
add-light-region-entryregionchunk:copy-content-pimmutable-p
whenimmutable-p
let
source
block-world-sourceworld
maphash
lambda
keyentry
declare
ignoreentry
dolist
let
neighbor
declare
dynamic-extentneighbor
unless
gethashneighbor
light-region-entriesregion
setf
gethash
light-region-absent-semanticsregion
absent-chunk-light-semanticssourceworldkeydirection
light-region-entriesregion
region
defunmake-light-candidate
world

A lazily populated region whose entries start from current fields.

%make-light-region:worldworld:space
block-world-spaceworld
:ensure-entry
lambda
regionkey
multiple-value-bind
chunkpresent-p
whenpresent-p
add-light-region-entryregionchunk:from-field-pt
defunlight-region-locate-components
regionxyz

Resolve scalar world components to ENTRY, OFFSET, and availability.

multiple-value-bind
chunk-xchunk-ychunk-zlocal-xlocal-ylocal-z
voxel-space-decompose-components
light-region-spaceregion
xyz
let*
key
make-chunk-coordinatechunk-xchunk-ychunk-z
entry
or
gethashkey
light-region-entriesregion
let
ensure
light-region-ensure-entryregion
andensure
funcallensureregionkey
ifentry
valuesentry:available
valuesnilnil:unavailable
declaim
defunlight-region-locate
regioncoordinate

Resolve a WORLD-COORDINATE to (VALUES ENTRY OFFSET) or NIL when absent.

multiple-value-bind
entryoffsetavailability
light-region-locate-componentsregion
world-coordinate-xcoordinate
world-coordinate-ycoordinate
world-coordinate-zcoordinate
declare
ignoreavailability
valuesentryoffset
defmethodlocate-chunk-window-site
regionlight-region
xyz
defunlight-region-opacity
entryoffset
aref
aref
light-region-entry-indicesentry
offset
defunlight-region-neighbor-resident-p
regioncoordinatedirection
let
neighbor
chunk-coordinate-neighborcoordinatedirection
declare
dynamic-extentneighbor
if
light-region-absent-semanticsregion
nth-value1
gethashneighbor
light-region-entriesregion
nth-value1
world-chunk-at-coordinate
light-region-worldregion
neighbor
defunlight-region-absent-boundary-semantics
regionkeydirection
let
captured
light-region-absent-semanticsregion
ifcaptured
gethashcaptured
let
world
light-region-worldregion
absent-chunk-light-semantics
block-world-sourceworld
worldkeydirection
defunlight-region-provisional-p
regionkey

Whether chunk key currently borders any :UNKNOWN residency boundary.

let
world
light-region-worldregion
declare
ignoreworld
loopfordirectionin*voxel-face-directions*thereis
defunmap-entry-face-sites
entrydirectionfunction

Call function with OFFSET and borrowed LOCAL for one face of entry.

LOCAL has dynamic extent and must be copied before function retains it.

let
do-chunk-domain-face
offsetlocaldomaindirection
funcallfunctionoffsetlocal
defparameter*voxel-light-solver*:compiled

The voxel-light program selected for production solves.

Only :COMPILED is implemented by the runtime. The legacy implementation is loaded explicitly by the LUVCRAFT/LIGHT-REFERENCE system as a differential test oracle; unsupported names signal through the closed EQL dispatch. #X7Q90E #PJY6E1 #K3WRD3

defgenericsolve-light-region-using
solverregion&key&allow-other-keys
:documentation

Solve captured region with the explicitly implemented voxel-light solver.

There is deliberately no default method: unsupported solver names signal instead of silently selecting another implementation.

Publication compares complete candidate arrays against the chunk's current field and advances light revisions only: content revisions and the world revision are authored-data facts this derived domain must not touch.

defunlight-boundary-plane-changed-p
domainold-levelsnew-levelsdirection
do-chunk-domain-face
offsetlocaldomaindirection
declare
ignorelocal
when
/=
arefold-levelsoffset
arefnew-levelsoffset
nil
defunpublish-light-region
region

Install every changed candidate field; return the changed chunks.

let
changednil
maphash
lambda
keyentry
let*
chunk
light-region-entry-chunkentry
field
block-chunk-light-fieldchunk
state
if:provisional:stable
cond
nullfield
let
replacenew-sky
replacenew-block
setf
chunk-light-field-statefield
state
chunk-light-field-revisionfield
1
let
revisions
chunk-light-field-boundary-revisionsfield
dotimes
face6
setf
arefrevisionsface
1
setf
block-chunk-light-fieldchunk
field
pushchunkchanged
t
let*
levels-changed-p
or
not
equalpold-skynew-sky
not
equalpold-blocknew-block
state-changed-p
not
eqstate
chunk-light-field-statefield
when
orlevels-changed-pstate-changed-p
let
revisions
chunk-light-field-boundary-revisionsfield
dolist
when
or
light-boundary-plane-changed-pdomainold-skynew-skydirection
light-boundary-plane-changed-pdomainold-blocknew-blockdirection
incf
arefrevisions
replaceold-skynew-sky
replaceold-blocknew-block
setf
chunk-light-field-statefield
state
incf
chunk-light-field-revisionfield
pushchunkchanged
light-region-entriesregion
changed
defunrelight-block-world
world

Solve and publish voxel light from scratch for world's resident chunks.

Returns the chunks whose published light changed. The selected production solver must have an explicit solve-light-region-using method.

Sparse light accessors, for inspectors and tests. Dense consumers (the mesher's snapshot halo) read the field arrays directly.

defunchunk-light-levels-at-coordinate
chunklocal

Return (VALUES SKY BLOCK STATE) at one LOCAL-COORDINATE in chunk.

let
field
block-chunk-light-fieldchunk
iffield
let
offset
chunk-domain-offset
block-chunk-domainchunk
local
values
chunk-light-field-statefield
values00:unlit
defunchunk-light-levels-at
chunkxyz

Scalar convenience wrapper around chunk-light-levels-at-coordinate.

let
declare
dynamic-extentlocal
defunworld-light-levels-at
worldxyz

Return sparse SKY, BLOCK, and STATE readings at world site X,Y,Z.

This is the inspector-scale light counterpart to world-block-at. Dense light consumers should bind a chunk field once rather than resolving every site.

multiple-value-bind
chunkoffsetavailability
ecaseavailability
:available
let
field
block-chunk-light-fieldchunk
iffield
values
chunk-light-field-statefield
values00:unlit
:unavailable
values00:unavailable
defclassluvcraft-lighting-state
world:initarg:world:readerlighting-state-world
dirty-cells:initform
make-hash-table:test#'equalp
:readerlighting-state-dirty-cells
arrivals:initform
make-hash-table:test#'equalp
:readerlighting-state-arrivals
departures:initform
make-hash-table:test#'equalp
:readerlighting-state-departures

Work counters, exposed so performance claims come from measurements.

cells-visited:initform0:accessorlighting-state-cells-visited
chunks-touched:initform0:accessorlighting-state-chunks-touched
publications:initform0:accessorlighting-state-publications
last-latency-seconds:initform0d0:typedouble-float:quantity
:quantity:lighting-reconciliation-duration:unit:second
:accessorlighting-state-last-latency-seconds
:documentation

Accumulated lighting dirtiness for one world, owned by its session.

Content edits and residency transitions feed this object through the world's hooks; reconcile-lighting settles the queues and publishes.

defunattach-lighting-state
world

Subscribe a fresh lighting state to world's content and residency hooks.

Chunks already resident at attachment are treated as arrivals, so the first reconcile lights a caller-built world without a separate protocol.

let
state
make-instance'luvcraft-lighting-state:worldworld
dolist
setf
gethash
chunk-domain-coordinate
block-chunk-domainchunk
lighting-state-arrivalsstate
t
setf
block-world-cell-change-hookworld
lambda
chunkxyz
declare
ignorechunk
setf
gethash
lighting-state-dirty-cellsstate
t
block-world-residency-change-hookworld
lambda
xyzevent
let
ecaseevent
:arrived
setf
gethashkey
lighting-state-arrivalsstate
t
:departed
setf
gethashkey
lighting-state-departuresstate
t
state
defunlighting-state-dirty-p
state
or
plusp
hash-table-count
lighting-state-dirty-cellsstate
plusp
hash-table-count
lighting-state-arrivalsstate
plusp
hash-table-count
lighting-state-departuresstate
defunlighting-state-residency-dirty-p
state

Whether state includes chunk arrivals or departures.

or
plusp
hash-table-count
lighting-state-arrivalsstate
plusp
hash-table-count
lighting-state-departuresstate
defunreconcile-lighting
state

Settle state's queues over a candidate and publish once.

Returns the chunks whose published light changed. Work runs on the calling owner thread; the counters record its actual cost so any move to captured producer batches is justified by measurement rather than guesswork.

let*
start
get-internal-real-time
world
lighting-state-worldstate
let
clrhash
lighting-state-dirty-cellsstate
clrhash
lighting-state-arrivalsstate
clrhash
lighting-state-departuresstate
incf
lighting-state-cells-visitedstate
visited
incf
lighting-state-chunks-touchedstate
hash-table-count
light-region-entriesregion
incf
lighting-state-publicationsstate
setf
lighting-state-last-latency-secondsstate
/
-
get-internal-real-time
start
coerceinternal-time-units-per-second'double-float
changed
defgenericreconcile-light-region-using
solverstateregion
:documentation

Settle state's dirty cells, departures, and arrivals over candidate region with the explicitly implemented incremental relighter named by solver. There is deliberately no default method: unsupported names signal rather than falling back to a different algorithm. #K3WRD3

defunworld-light-at-coordinate
worldcoordinate

Return (VALUES SKY BLOCK STATE) at coordinate, or zeros when absent.

multiple-value-bind
chunk-coordinatelocal
world-coordinate-chunk-and-local
block-world-spaceworld
coordinate
declare
dynamic-extentchunk-coordinatelocal
multiple-value-bind
chunkpresent-p
world-chunk-at-coordinateworldchunk-coordinate
ifpresent-p
values00:absent
defunworld-light-at
worldxyz

Scalar convenience wrapper around world-light-at-coordinate.

let
declare
dynamic-extentcoordinate