luv

Workshop wiki

voxel-fields-and-windows.org

Voxel fields and chunk windows

The seam is shared spatial meaning, not shared ownership #18MPLA

Luvcraft now has the right evidence for a spatial vocabulary to become more than an attractive analogy. Block content, derived light, live meshing neighborhoods, immutable worker snapshots, collision, and streaming all inhabit the same chunked voxel world. They should not become one object, but neither should each of them reconstruct what a cell address, local offset, neighbor, face, and chunk crossing mean.

The duplicated arrays are not the deepest problem. Separate fields often should be separate because they have different producers, owners, revisions, and storage. The architectural seam is that several such products currently rebuild the spatial substrate beneath those differences.

This is the first concrete client of the more general domain/bundle account in #D5M8BZ. It also sharpens that account: Bundle is not the whole missing abstraction. The useful decomposition is:

voxel space       how cell addresses and physical positions relate
finite domain     which local sites and storage offsets belong to one patch
field product     which distributed fact is materialized over those sites
chunk window      how a local query continues into other materializations
provenance        which incarnations and revisions justify the result

The aim is not to make the engine advertise advanced mathematics. It is to let lighting code speak about lighting, meshing code speak about surfaces, and physics code speak about bodies while sharing one honest account of where their data lives.

Mathematical ideas should improve the questions, not burden the names #EQ6GLY

Affine lattices, combinatorial topology, covers, sheaves, order-theoretic lattices, and discrete exterior calculus are useful sources of distinctions. They draw attention to placement, adjacency, incidence, locality, overlap, restriction, partial knowledge, and values associated with cells or faces. That does not yet earn those terms as source-level abstractions.

The implementation vocabulary can remain ordinary:

voxel-space
chunk-domain
chunk-window
neighbor
boundary-crossing
voxel-field
field-materialization
field-kernel

"Chunk window" is a particularly useful hacker-facing name for the part that resembles a cover or atlas. It says that an operation has a finite local view onto a larger chunked world without claiming a precise mathematical structure the implementation does not provide.

Likewise, today's cells and six-neighbor steps do not justify a general topological-space protocol. If future fluid or geometry work places values on faces, edges, and vertices and consumes explicit incidence operations, a cell complex or cochain vocabulary may then be earned by a real calculation.

Addressing and metric are two sides of voxel space #KV001P

The voxel world has a discrete addressing side:

It also has a physical metric side:

voxel-space already contains both a chunk shape and a cell-extent, and world-coordinate-cell-origin performs the first explicit address-to-position map. The distinction should become more pervasive rather than disappear into one universal kind of neighborhood.

Voxel light is step-based: an ordinary six-neighbor move loses a gameplay light level regardless of how many metres one cell represents. Physics is metric: velocity, acceleration, collision extents, and timestep require physical quantities. A diffusion stencil may need both adjacency and a spacing-dependent influence. Moppe's TerrainDomain is useful evidence: its four neighbors carry inverse-area influences, so a generic Laplacian derives the right physical dimension without knowing the grid spacing itself.

The shared concepts should therefore remain distinct:

ConceptAnswers
neighbor relationWhich sites are adjacent?
step displacementWhat physical displacement separates them?
neighborhood policyWhich adjacent or nearby sites does this operation use?

The current mesher and player simulation appear to treat one cell as one metre even though voxel-space admits another extent. That is a hypothesis to verify during the bridge work, not a settled bug claim. The durable rule is that a discrete cell address is not itself a metre-valued world position; the space performs that interpretation explicitly. #M8W6RP already records the corresponding quantity distinction.

Vec3 is a representation, not one spatial meaning #7K8UBF

The luvcraft source contains several visibly three-component families, but an audit shows that they should not all become the same abstraction:

FamilyCPU representationMeaning lives in
cell, chunk, and local addressesdistinct integer structuresnominal coordinate type
primitive face stepsvoxel-directiondisplacement type and six shared values
camera/player positions and velocityvec3owning slot or function name
ray, camera basis, and sun directionvec3operation and argument name
colourssimple vectorssky/color calculation
mesh attributes and shader valuesspecialized arrays and :vec3 lanesvertex layout and shader quantity metadata

The vec3 structure in LUV.ARITHMETIC.LISP.VEC3 is therefore deliberately modest. It bundles continuous CPU components, supplies component, scale, dot, cross, length, and normalization operations, and extends the open Lisp arithmetic protocol without making that protocol name the representation. LUVCRAFT.WORLD imports it but does not own or re-export it. The representation does not claim that a point is a velocity, that either is already measured in metres, or that an RGB triple belongs to spatial algebra. camera-position and player-velocity name different semantic owners even though both use the same transparent representation.

That distinction gives the readable code the useful part of structification: camera and player objects no longer maintain parallel x, y, and z slots; rays no longer accept arbitrary sequences; camera basis and shadow calculations no longer duplicate positional list arithmetic; persistence converts a vec3 to an external list only at the file boundary. Axis-specific collision and voxel traversal still use component accessors where the axis is the algorithm.

This also keeps the allocation claim narrow. A retained camera position or velocity naturally has indefinite extent. A short-lived vec3 may use the same inline-constructor and dynamic-extent technique documented in #B3UEVE, but the migration does not require every vector calculation to prove zero allocation before earning a readable value type.

defstruct (vec3 (:constructor %make-vec3 (x y z))) vec3.lisp:8
defstruct
vec3
:constructor%make-vec3
xyz

A transparent CPU spatial triple, deliberately separate from its meaning. See #7K8UBF.

x0:typereal
y0:typereal
z0:typereal

A chunk domain owns local traversal, not world truth #E4T0PD

A chunk-domain should be the one place that knows the finite patch's local shape and storage order. Its useful operational vocabulary is approximately:

domain-cardinality
domain-index / domain-offset
do-domain-sites
step-domain-site
do-domain-face

step-domain-site should report either another local site or a boundary crossing. It should not decide that crossing the east face reaches air, stone, open sky, an unloaded chunk, or an error. Those are facts or policies of the environment in which the domain is being used.

Nor should one neighborhood become intrinsic to every chunk calculation. Lighting wants six face neighbors; ambient occlusion wants side and diagonal samples; meshing wants a face neighbor plus corner stencils; collision wants the cells intersecting a volume. The domain supplies primitive traversal and boundary facts. Explicit policies assemble the neighborhoods algorithms actually mean, following the separation that survived in Moppe's bundle_operations.hh (#E4N7PX).

Coordinates should be ordinary nominal values throughout the code, including dense traversal. The distinction worth preserving is not "objects outside, components inside" but lifetime: a queue or hash key needs an indefinite-extent coordinate, while a loop-local coordinate can have dynamic extent. Compiler-visible iteration macros can provide the latter without a heap allocation or a per-cell call across a compilation-unit boundary.

That requirement applies to adjacency as well as whole-domain and face iteration. A function which returns a fresh neighboring coordinate has to give it indefinite extent before its caller can decide whether to retain it. The useful hot-path shape is therefore a macro which binds one dynamic-extent destination around the consumer body; only a consumer which actually enqueues the destination copies it. Crossing arithmetic remains the domain's job, window lookup still occurs only for a crossing, and the caller still supplies the neighborhood policy.

defmacro with-chunk-domain-step world.lisp:334
defmacrowith-chunk-domain-step
offsetdestinationcrossing
domainlocaldirection&bodybody

Execute body for one primitive step from local inside domain.

offset, DESTINATION, and CROSSING are bound as by step-chunk-domain-site, but DESTINATION has dynamic extent and must be copied before body retains it. The compiler-visible lifetime keeps rejected adjacency probes off the heap. #E4T0PD

let
domain-value
gensym"DOMAIN"
local-value
gensym"LOCAL"
direction-value
gensym"DIRECTION"
shape
gensym"SHAPE"
width
gensym"WIDTH"
height
gensym"HEIGHT"
depth
gensym"DEPTH"
crossing-x
gensym"CROSSING-X"
crossing-y
gensym"CROSSING-Y"
crossing-z
gensym"CROSSING-Z"
local-x
gensym"LOCAL-X"
local-y
gensym"LOCAL-Y"
local-z
gensym"LOCAL-Z"
`
let*
,domain-value,domain
,local-value,local
,direction-value,direction
check-type,local-valuelocal-coordinate
check-type,direction-valuevoxel-direction
chunk-domain-offset,domain-value,local-value
let*
,shape
voxel-space-chunk-shape
chunk-domain-space,domain-value
,width
chunk-shape-width,shape
,height
chunk-shape-height,shape
,depth
chunk-shape-depth,shape
multiple-value-bind
,crossing-x,local-x
floor
+
local-coordinate-x,local-value
voxel-direction-dx,direction-value
,width
multiple-value-bind
,crossing-y,local-y
floor
+
local-coordinate-y,local-value
voxel-direction-dy,direction-value
,height
multiple-value-bind
,crossing-z,local-z
floor
+
local-coordinate-z,local-value
voxel-direction-dz,direction-value
,depth
let
,destination
make-local-coordinate,local-x,local-y,local-z
,offset
+,local-x
*,width
+,local-y
*,height,local-z
,crossing
and
not
zerop
+
abs,crossing-x
abs,crossing-y
abs,crossing-z
,direction-value
declare
dynamic-extent,destination
,@body
defmacro do-chunk-window-neighbors world.lisp:449
defmacrodo-chunk-window-neighbors
offsetdestinationcrossingdirectionmaterializationavailabilitywindowdomainlocaldirections&optionalresult
&bodybody

Execute body for the DIRECTIONS neighboring LOCAL through WINDOW.

DIRECTIONS is the caller's explicit neighborhood policy. DESTINATION has dynamic extent on each iteration and must be copied before body retains it. Interior steps remain domain arithmetic; window dispatch occurs only at an actual chunk crossing. #E4T0PD

let
window-value
gensym"WINDOW"
domain-value
gensym"DOMAIN"
local-value
gensym"LOCAL"
directions-value
gensym"DIRECTIONS"
`
let
,window-value,window
,domain-value,domain
,local-value,local
,directions-value,directions
dolist
,direction,directions-value,result
with-chunk-window-step
,offset,destination,crossing,materialization,availability
,window-value,domain-value,local-value,direction
,@body

Coordinate values can be stack-temporary #B3UEVE

SBCL's stack-allocation rules make defstruct a good fit for the small spatial values in this layer. A user-defined structure can be stack allocated when its defstruct constructor was declared inline before the definition and the receiving binding is declared dynamic-extent. The declaration is a lifetime promise, not an analysis result: the value must not escape through a queue, hash table, closure, return value, or retained callback argument.

The manual's x86/x86-64 limitation concerns structures with raw slots. SBCL defines those as particular unboxed float, complex-float, or word slots, and explicitly excludes fixnums from the category. The coordinate structures use ordinary tagged integer slots, so this limitation does not apply on the current ARM64 target.

The live SBCL 2.6.7 image confirms the distinction under the project's ordinary compiler policy (all qualities at 1). In otherwise identical one-million-iteration loops, an ordinary three-integer coordinate structure reported 48,182,016 bytes through sb-ext:get-bytes-consed, while the dynamic-extent structure reported 0. A three-element Common Lisp vector declared dynamic extent likewise reported 0. After warm-up, the actual do-chunk-domain-sites macro traversed 4,096,000 coordinate values and reported 0 bytes consed.

A three-dimensional coordinate certainly has vector-shaped representation, but not every such value has vector semantics. A world coordinate is an affine cell address, a chunk coordinate names another lattice, a local coordinate is bounded by a domain, and a voxel direction is a displacement. Using four defstruct types retains those distinctions and named accessors; using bare Common Lisp vectors would collapse them back into positional components. Dense storage remains in specialized arrays. These structures name the small values used to address that storage, and dynamic extent makes their short lifetime explicit where it matters.

defmacro do-chunk-domain-sites world.lisp:581
defmacrodo-chunk-domain-sites
offsetlocaldomain&optionalresult
&bodybody

Execute body for every site in DOMAIN, in dense storage order.

LOCAL is a DYNAMIC-EXTENT LOCAL-COORDINATE and must not be retained after body returns. This gives dense algorithms a nominal coordinate without one heap allocation per site. See #B3UEVE.

let
domain-value
gensym"DOMAIN"
shape
gensym"SHAPE"
width
gensym"WIDTH"
height
gensym"HEIGHT"
depth
gensym"DEPTH"
x
gensym"X"
y
gensym"Y"
z
gensym"Z"
`
let*
,domain-value,domain
,shape
voxel-space-chunk-shape
chunk-domain-space,domain-value
,width
chunk-shape-width,shape
,height
chunk-shape-height,shape
,depth
chunk-shape-depth,shape
,offset0
dotimes
,z,depth
dotimes
dotimes
let
declare
dynamic-extent,local
,@body
incf,offset
,result

A chunk window continues a local domain #YUBB7X

A local domain can say that a step crossed its boundary. A chunk window answers what spatial materialization, if any, can continue the query:

flowchart LR
  A["local site"] --> B["domain step"]
  B -->|"stays local"| C["local offset"]
  B -->|"crosses a face"| D["chunk window"]
  D --> E["neighbor materialization"]
  D --> F["open / closed / unknown"]
  D --> G["unavailable"]

The window reports availability; the subsystem interprets it. Lighting may distinguish open sky, closed world, and unknown terrain. Meshing may treat an absent sample as air, solid, or an error. Physics may treat unknown support as solid or suspend a body. Ecology may call the reading unavailable rather than zero. Moving those choices into the domain would turn streaming state into terrain truth.

Different windows need not share one representation. They need a small aggregate protocol for operations such as locating a cell, resolving a boundary crossing, and borrowing a requested field product. Field-specific facades such as sample-block-at and sample-light-at can remain readable interfaces above that mechanism.

defmacro with-chunk-window-step world.lisp:411
defmacrowith-chunk-window-step
offsetdestinationcrossingmaterializationavailability
windowdomainlocaldirection&bodybody

Execute body for one domain step continued through window when necessary.

DESTINATION has dynamic extent. MATERIALIZATION and AVAILABILITY are selected through locate-chunk-window-site only for a boundary crossing. #YUBB7X

let
window-value
gensym"WINDOW"
domain-value
gensym"DOMAIN"
local-value
gensym"LOCAL"
direction-value
gensym"DIRECTION"
local-offset
gensym"LOCAL-OFFSET"
world-x
gensym"WORLD-X"
world-y
gensym"WORLD-Y"
world-z
gensym"WORLD-Z"
`
let
,window-value,window
,domain-value,domain
,local-value,local
,direction-value,direction
with-chunk-domain-step
,local-offset,destination,crossing
,domain-value,local-value,direction-value
multiple-value-bind
,materialization,offset,availability
if,crossing
multiple-value-bind
,world-x,world-y,world-z
chunk-domain-world-components,domain-value
local-coordinate-x,local-value
local-coordinate-y,local-value
local-coordinate-z,local-value
locate-chunk-window-site,window-value
+,world-x
voxel-direction-dx,direction-value
+,world-y
voxel-direction-dy,direction-value
+,world-z
voxel-direction-dz,direction-value
valuesnil,local-offset:local
,@body

The current program already contains four windows #WUB581

The proposed vocabulary names structures that already work:

Current structureWindow it providesSpatial work it currently owns
block-worldlive resident worldcoordinate decomposition, resident lookup, absent status
block-mesh-neighborhoodresolved 3\times3\times3 windowrelative chunk decomposition and repeated dense offsets
block-mesh-snapshotimmutable one-cell haloexplicit halo domain, expanded coordinates, and one fixed columnar materialization
light-regioneager or lazy solver atlasboundary-only region resolution, entry-local offsets, and lazy entry capture

The duplication is visible in current source rather than inferred from equal array lengths. light-region-locate remains the cold adapter from retained world coordinates, but propagation and unlighting retain an entry and local site, then use continue-chunk-window-site so interior neighbors stay in domain arithmetic. map-entry-face-sites delegates face offsets to do-chunk-domain-face. In luvcraft/mesher.lisp, block and light access through the resolved neighborhood share relative decomposition and offset arithmetic, while the immutable snapshot shares one block-mesh-halo-domain and generated columnar materialization across content-code, sky-level, and block-level storage (#LC5ULQ).

This is an unusually good refactoring target because the algorithms around the arithmetic are already distinct and tested. The work can remove spatial duplication while leaving light propagation, AO sampling, face visibility, snapshot ownership, and stale-result rejection recognizable.

Field identity, value meaning, and representation are independent #FKAV5Y

A field definition says which distributed fact one column represents. Its value quantity says what a reading at one site means. Its representation says how those readings are stored. These are three different identities.

For example, sky light and block light are different fields: they have different sources and may have different invalidation rules. They may also use distinct named quantity specifications. Both currently occupy u8 arrays, but equal representation makes neither the fields nor their values interchangeable.

Conversely, two field roles may deliberately carry the same quantity specification while remaining separate columns. One semantic light value could also be represented as constant zero, a packed nibble, u8 levels, normalized float, SIMD-expanded floats, or a GPU lane without changing the field's meaning.

A first field definition might need only:

field identity
site kind
value quantity specification
default or missing-value meaning
legal range or encoding
representation policy

A field materialization then binds that definition to a finite domain and actual storage. Product-wide revisions, boundary revisions, and stability state belong to the materialization or its owning derived product, not to every site value.

Block opacity exposes why these distinctions matter. The atlas's normalized :opacity is a rendering proportion. block-light-opacity is documented as a 0..15 propagation cost. Calling both opacity does not make them one quantity. A field experiment should probably rename the latter meaning to something like propagation loss or light-step cost before using arithmetic typing to bless an accidental equivalence.

defclass voxel-field-definition world-fields.lisp:10
defclassvoxel-field-definition
name:initarg:name:readervoxel-field-definition-name
site-kind:initarg:site-kind:readervoxel-field-definition-site-kind
missing-value-semantics:initarg:missing-value-semantics:readervoxel-field-definition-missing-value-semantics
legal-value-type:initarg:legal-value-type:readervoxel-field-definition-legal-value-type
representation-policy:initarg:representation-policy:readervoxel-field-definition-representation-policy
revision:initform
gensym"FIELD-DEFINITION-"
:readervoxel-field-definition-revision
:documentation

One distributed fact's identity and per-site meaning. #FKAV5Y

The inherited represented-value declaration describes a reading after its storage encoding has been resolved. REPRESENTATION-POLICY separately names the aggregate encoding, such as a palette-index column or a u8 level array.

Domain, materialization, and revision have different identities #HAUW7L

A chunk removed and reinstalled at the same chunk coordinate covers the same spatial domain, but it is not the same resident materialization. A field can then change several times within that incarnation. Luvcraft's same-key replacement tests already rely on this distinction when withdrawing the old chunk's propagated light.

At least three identities must therefore remain visible:

domain identity           which sites these are
materialization identity  which resident incarnation supplies them
revision identity         which version of its fields was observed

Bundle compatibility checks domain identity rather than equal cardinality. A mesh, collider, or light publication proof additionally records the materialization incarnations and field revisions it observed. Folding all three into one "domain version" would erase the correctness property the current stale-result machinery carefully preserves.

This is also why one universal voxel bundle is the wrong endpoint. Block content, voxel light, fluids, and ecological readings can be sibling materializations over compatible domains while retaining their own owners and validity conditions (#A6X2RT).

Field kernels bridge domains to semantic arithmetic #ADR040

Luv's arithmetic work already has the layers this design needs:

Shaders therefore do not own meaningful arithmetic. They are one realization of it. The missing bridge says:

This field carries this value meaning over these sites in this
representation; choose one checked operation and run it over borrowed storage.

A field kernel should connect field definitions to a checked arithmetic definition, select a scalar or wide realization once, borrow the arrays, and then run an ordinary loop or worklist without per-cell CLOS dispatch.

flowchart TB
  A["field definitions + finite domains"] --> B["checked kernel boundary"]
  C["arithmetic definition"] --> B
  B --> D["borrowed raw arrays"]
  B --> E["scalar / SIMD / GPU realization"]
  D --> F["closed loop or worklist"]
  E --> F

This is the Lisp analogue of Moppe storing mp-units values in typed Bundle columns, but it need not copy the C++ technique. Inspectable definitions and aggregate checks can keep the semantic information outside unboxed runtime lanes, as #B5L7VG anticipates.

Physics is the obvious next numerical client, but not another reason to add columns to a universal voxel record. Collision traversal should remain domain/window code which enumerates occupied cells and constructs contacts; integration, acceleration, and eventual impulse laws can be checked arithmetic over body fields. Bodies and contacts then retain their own finite domains as #Y5H8KC describes, while the block world remains the static environment they query.

Lighting should use a checked local law, not become an arithmetic graph #WEE8P5

The light solver is the best first proof because it already separates block content, two light fields, palette-derived attenuation and emission tables, boundary availability, fixed-point work, publication, and revision tracking.

The arithmetic DSL should describe a local transfer law, not the entire algorithm. In rough form one edge computes:

candidate = attenuate(source level, propagation loss, direction)
destination = max(destination, candidate)

Chunk lookup, residency, queues, removal waves, scheduling, publication, and revision tracking remain lighting control structure. The chunk window supplies the destination site; field materializations supply typed storage; the checked local law computes one influence; the worklist decides when to visit again.

There is an open semantic choice at the CPU/mesh boundary. The stored 0..15 level may be only an encoding of a normalized light proportion, making division by 15 a representation conversion. Or one gameplay light step may be a meaningful scaled dimensionless unit, making the propagation rule and conversion to :one explicit arithmetic. "Lose one level per ordinary step" is currently a game rule, so the second reading is plausible, but the first field experiment should force and record the decision rather than hide it in u8 storage.

If ordinary subtraction cannot truthfully express attenuation, add a named operation such as attenuate-light through the open EQL-specialized operator protocol. Do not weaken - until two differently named integers happen to type-check.

The fixed-point algebra should wait for a second client #BA9MCA

Light has two lattice-like structures. Its sites form a spatial lattice under six-neighbor adjacency. Its levels form a finite order in which max is the join. From-scratch propagation repeatedly joins attenuated neighbor contributions until it reaches a fixed point. Incremental removal first over-removes a region, then rebuilds the monotone closure from surviving sources.

That suggests a later reusable shape:

worklist fixed point
  domain and window
  input and output fields
  sources
  neighborhood policy
  edge transfer
  join

Distance transforms, reachability, influence propagation, or ecological fields might eventually share it. Extracting this solver before a second client would be a framework searching for evidence. Domain stepping, window resolution, field meaning, and kernel boundaries already have multiple real clients and should come first.

The sheaf-like interpretation should remain similarly informal. Chunks are local patches, halos resemble restrictions, and snapshots collect compatible local readings. But core chunk domains do not overlap, residency introduces unknown values, and lighting solves a nonlocal equation rather than merely gluing existing answers. "Field materialization" and "chunk window" are more operationally exact today.

The later source study #NXXOQT supplies another kind of evidence: a small Haskell engine in which BFS, A*, and Cheney collection share explicit frontier, memo, admission, and two-phase materialization roles. The terminal-wall component in #K3PCP3 is now a deliberately simpler adjacency client beside lighting. Together they narrow the reusable target to packed frontier storage and iteration laws while leaving fixed-point relaxation as one client-owned policy.

Chunk hierarchy and level of detail are related but not identical #TDEVBM

A residency hierarchy groups the same underlying cells into chunks, regions, or superchunks for lookup, persistence, scheduling, and invalidation. It does not necessarily make the world coarser.

Level of detail introduces different sites or different field meanings. A coarse site may carry occupancy fraction, dominant material, surface bounds, average radiance, or conservative collision information rather than one categorical block state. #O3P8NV develops why its schema may differ as well as its spacing.

An octree can host either concern, which is why they are easy to conflate. The semantic test is:

A later hierarchical window may resolve a request from an ordinary resident chunk, an ancestor cache, or a coarse materialization. Local algorithms need not know the lookup structure, but the returned field meaning, resolution, and confidence must remain honest.

Not everything spatial is a voxel field either. Moisture, temperature, smell, or habitat suitability may be cell fields. A critter is more likely a persistent entity carrying a position, perhaps indexed by spatial buckets. Vegetation can have both density fields and individual nearby plants. These systems can share voxel-space and chunk-window protocols without sharing one storage abstraction.

The Lisp shape is semantic objects around dense data #M0REWV

The idiomatic arrangement follows the project's existing CLOS practice:

The first experiment should not begin with bundle-class. Ordinary inspectable field definitions attached to the existing block-content and light columns can reveal what metadata and redefinition behavior are actually needed. A metaclass becomes useful only if real definitions repeatedly want class-level schema, allocation, migration, and inspector support.

Live redefinition adds one requirement that Moppe gets from C++ compilation: a materialization must know which field-definition revision made its bytes meaningful. Redefining :sky-light must either remain representation compatible, visibly invalidate existing arrays, or migrate them. It must not silently reinterpret live storage. Compiled kernels likewise need dependency revisions and last-known-good replacement, following the existing shader discipline.

A staged concretization #BA1QCS

The following is a sequence to refine from evidence, not a Gantt chart.

  1. State the value vocabulary. Complete #9ZK7Q7 so luvcraft's existing amounts and points stop relying on use-site annotations. Decide whether a gameplay light step is a unit or a storage encoding; distinguish propagation loss from texture opacity.
  1. Extract local spatial operations. Give chunk-domain heap-allocation-free world/chunk/local decomposition, primitive stepping with boundary results, storage-order traversal, and face enumeration. Migrate light-region and meshing paths without changing their algorithms.
  1. Name the window protocol. Let the live world, resolved meshing neighborhood, immutable halo, and light-region atlas resolve locations or crossings through their existing representations. Preserve semantic facades such as sample-block-at and sample-light-at.
  1. Attach ordinary field definitions. Describe the existing block-content, sky-light, and block-light columns without changing their arrays. Keep field identity, value quantity, representation, product state, and provenance distinct.
  1. Share one production calculation. Refine #D1R2NK with a domain-shaped client. Light decoding or a named light-response law is modest; a transfer law is more revealing if both CPU tooling and a shader genuinely use it.
  1. Earn a field kernel. Once one scalar field operation is stable, select its representation once and run it over borrowed arrays. A native SIMD realization follows a real measured kernel (#VKLLPR), not the vocabulary design.

Each step should delete a concrete duplication or make a concrete mistake unrepresentable. None requires a universal bundle, a generalized fixed-point solver, a sheaf API, per-cell objects, or a broad rewrite of the working light system.

DONE Make chunk traversal one owned vocabulary #K3KZTG

Intent: establish the smallest reusable spatial substrate beneath the current light and meshing products. Add heap-allocation-free decomposition, primitive local stepping with explicit boundary crossings, storage-order site enumeration, and face enumeration to chunk-domain or one narrowly related voxel-domain protocol. Rewrite light-region-locate, map-entry-face-cells, the resolved meshing-neighborhood accessors, and halo capture to consume those operations while retaining their representations and policies.

Evidence: voxel-space-decompose-components owns Euclidean decomposition, while chunk-domain owns offset inversion, local-to-world mapping, primitive stepping with explicit chunk crossings, storage-order enumeration, and face enumeration. World, chunk, local, and direction values now give that substrate one nominal vocabulary rather than parallel positional triples.

light-region-locate and map-entry-face-sites no longer contain their own division, dense-offset, or face-plane formulas. Light seeding and boundary re-entry consume domain site traversal and primitive stepping. The resolved meshing neighborhood now has one locator shared by block and light reads; the snapshot has one halo-offset operation shared by capture and both field accessors. Six cached origin/extent slots disappeared from each snapshot, while the world, light region, neighborhood, and snapshot remain separate inspectable representations.

The full make test suite passes, including vertical and lateral light seams, open/closed/unknown boundaries, randomized incremental/reference equality, same-key replacement, stale mesh products, and immutable halo equivalence. make smoke produces edc597168a3ec79a04c6d2af53c5b1a5, byte-identical to the render from starting commit b9fa93d.

Done: one owned domain vocabulary now supplies local traversal to both lighting and meshing. No field schema, metaclass, generalized solver, or per-cell object was introduced to obtain the deletion.

defun block-mesh-neighborhood-locate mesher.lisp:233
defunblock-mesh-neighborhood-locate
neighborhoodxyz

Resolve one nearby world site to its resident chunk and dense offset.

The 3x3x3 window owns availability; voxel-space and chunk-domain own the decomposition and storage order beneath it. See #K3KZTG.

let*
domain
block-mesh-neighborhood-domainneighborhood
space
chunk-domain-spacedomain
center
chunk-domain-coordinatedomain
multiple-value-bind
chunk-xchunk-ychunk-zlocal-xlocal-ylocal-z
let
dx
-chunk-x
chunk-coordinate-xcenter
dy
-chunk-y
chunk-coordinate-ycenter
dz
-chunk-z
chunk-coordinate-zcenter
when
and
<=-1dx1
<=-1dy1
<=-1dz1
let
chunk
aref
block-mesh-neighborhood-chunksneighborhood
whenchunk
valueschunk
chunk-domain-offset-components
block-chunk-domainchunk
local-xlocal-ylocal-z

DONE Give dense traversal coordinate values #R3T7YA

Intent: replace the positional x y z and dx dy dz protocols exposed by the first extraction with nominal coordinate and direction values. Make dense iteration a macro so those values can be constructed and consumed at the call site, and distinguish its borrowed dynamic-extent values from the coordinates lighting intentionally retains.

Evidence: do-chunk-domain-sites and do-chunk-domain-face expand closed storage-order loops whose local coordinates are declared dynamic-extent. The mesher's visibility and emission passes consume those values directly. Lighting propagation queues, removal records, dirty-cell sets, arrival and departure sets, region keys, face seeding, and boundary re-entry now carry world-coordinate, chunk-coordinate, local-coordinate, or voxel-direction values according to meaning. A retained coordinate is copied out of a dynamic-extent traversal value deliberately.

The current ARM64 stack-allocation evidence is recorded in #B3UEVE. The focused world suite passes 14 tests and the full luvcraft suite passes 41, including reference-light convergence, chunk seams, same-key replacement, stale mesh products, and immutable halo equivalence.

Done: the compiler-visible dense path and the readable nominal path are now the same path. Components remain available through accessors at arithmetic leaves; they are no longer the protocol repeated across lighting and meshing.

DONE Bundle continuous spatial triples #7A5HI4

Intent: replace the parallel camera/player position and velocity components, ad hoc ray sequences, and local camera-basis vector helpers with one small continuous CPU representation. Preserve nominal integer coordinates and directions, semantic shader quantities, packed mesh lanes, and colours as different concepts and representations.

Evidence: fly-camera owns one camera-position value; block-world-player owns player-position and player-velocity values; raycasting requires a vec3 origin and direction; cell extent and address-to-position mapping use vec3; the sun and camera basis share the same elementary vector operations. Block-face normals now reuse voxel-direction instead of becoming generic vectors. Persistence retains its portable list form at the external boundary.

The source audit and boundary rationale are recorded in #7K8UBF. The focused world suite passes 15 tests and the full luvcraft suite passes 42. A camera with double-float CPU components is explicitly covered at the single-float GPU uniform boundary. The standalone smoke render is SHA-256 0562804c1a3a4aaed5cc2865d0ca556b92ca231ab339f3310b21a8515b07d2c0, byte-identical to the render from starting commit 07bf2ed.

Done: related continuous components travel together through the CPU code while the program still says which triples are addresses, directions, colours, and GPU representations.

DONE Name the chunk-window protocol #L84JCX

Intent: expose the shared aggregate operation already visible in the live world, resolved meshing neighborhood, immutable halo snapshot, and light-region atlas. Give those four concrete representations a small protocol for locating a world site or resolving a domain crossing, while keeping availability distinct from each subsystem's absent/open/closed/unknown policy. Preserve sample-block-at and sample-light-at as readable field-specific facades.

Evidence to gather:

Done when: location and crossing continuation have one honest protocol across the four current windows, subsystem policy remains in semantic facades, and at least one remaining window-specific lookup duplication is deleted.

Evidence: locate-chunk-window-site now reports only a materialization, representation-owned offset, and :available or :unavailable. Methods on block-world, block-mesh-neighborhood, block-mesh-snapshot, and light-region retain their hash table, resolved 27-chunk vector, halo arrays, and lazy atlas respectively. continue-chunk-window-site performs pure chunk-domain arithmetic for an interior step and dispatches to the window only when the step crosses; its test adds a fifth recording window by one method and proves that interior traversal never asks it.

The field-specific sample-block-at and sample-light-at facades still own the meaning of absence. Hot neighborhood, snapshot, and light-region paths call representation-specific inline locators rather than nesting a new CLOS decision per sample. The snapshot locator now supplies both block and light sampling, deleting their duplicated halo-presence test, while light-region-locate-components shares scalar decomposition with the public window method and avoids the old temporary local-coordinate value. The luvcraft suite exercises all four windows, boundary continuation, meshing, lighting, production, and deterministic world behavior.

The lighting runtime now carries light-region-entry plus a retained local-coordinate in both addition and removal frontiers. An exact regression records zero locate-chunk-window-site calls for an interior six-neighbor visit and one call for a site on one face, for both propagation and unlighting. World coordinates remain the identity of dirty and removed cells only at the colder reconciliation boundary. Each entry retains its chunk-domain; publication compares faces with do-chunk-domain-face, including a non-cubic 3\times4\times5 test, and light-region's redundant width, height, and depth caches are gone.

On the fixed 27-chunk, seed-121 SLY fixture, five warmed solves still visit 214,056 sites. Median consing fell from 87,965,440 to about 74.9 million bytes (about 15%), while median elapsed time rose from 0.178 seconds to 0.198--0.208 seconds on the same machine. A sampled run placed 9 of 18 inclusive samples in continue-chunk-window-site, so this round records an allocation win and a boundary-lookup invariant, not a latency win. The focused luvcraft suite passes 58 tests, strict parinfer accepts every touched Lisp file, and a smoke capture from the production SLY image remains byte-identical at edc597168a3ec79a04c6d2af53c5b1a5.

deftest compiled-light-drain-allocates-nothing-per-relation light-tests.lisp:344
deftestcompiled-light-drain-allocates-nothing-per-relation

A warmed drain over pre-grown frontier storage allocates only at chunk crossings, where the window resolves a coordinate key (#L84JCX), never per site or per relation. The proof world exposes about 68,000 relations and 4,000 crossings; one cons per relation would exceed a megabyte.

let*
dotimes
round3
let*
luvcraft::seed-compiled-sky-boundariesskyregionfrontierexecution
let
observation
make-runtime-observation
setfbytes
runtime-observation-bytes-consedobservation
relations
luvcraft.frontier:frontier-execution-relationsexecution
crossings
luvcraft.frontier:frontier-execution-crossingsexecution
ok
>relations60000
ok
<bytes
+
*641024
*128crossings
defun continue-chunk-window-site world.lisp:567
defuncontinue-chunk-window-site
windowdomainlocaldirection

Step local and resolve window only when the step crosses domain.

Return the destination OFFSET, wrapped LOCAL-COORDINATE, and crossing as step-chunk-domain-site does, followed by the neighboring MATERIALIZATION and its AVAILABILITY. A local step returns NIL and :local for the last two values. The one generic window decision therefore occurs only at an aggregate boundary, never for every site in a dense traversal. #L84JCX

with-chunk-window-step
offsetdestinationcrossingmaterializationavailability
windowdomainlocaldirection
valuesoffset
copy-local-coordinatedestination
crossingmaterializationavailability

DONE Keep rejected neighbors at dynamic extent #KRU4RZ

Intent: make the lifetime distinction in #E4T0PD visible to SBCL while preserving the chunk-domain and chunk-window boundaries from #L84JCX. A six-neighbor visit should use one body-scoped destination at a time and copy a coordinate only when the algorithm retains that destination.

Evidence: with-chunk-domain-step exposes local stepping without returning its temporary, with-chunk-window-step adds crossing-only window resolution, and do-chunk-window-neighbors takes an explicit direction sequence. The lighting addition and removal paths now retain a destination only when they enqueue it; boundary reseeding uses the same body-scoped step. The old step-chunk-domain-site and continue-chunk-window-site functions remain as colder adapters which deliberately copy their result.

On SBCL 2.6.6, the fixed seed-121 81-chunk solve still performs exactly 2,430,167 worklist visits. Before this change it took about 1.79 seconds and consed 863.6 MB; three post-change solves each took 1.23 seconds and consed 264.1--264.2 MB. Thus rejected adjacency coordinates accounted for roughly 600 MB and a third of this fixture's elapsed time, without accounting for any of the repeated fixed-point work.

Done when: interior visits perform no window lookup, one-face visits perform one lookup, explicit copies survive subsequent iterations, lighting results remain unchanged, and the full test and deterministic smoke evidence pass. The complete make test path passes, including SPIR-V validation and all luv, luvcraft, and wiki suites. Two standalone smoke builds produced the identical SHA-256 0c85d4550638418cb88d507c6541a49147aeeea5697175d13904942b65a689ec.

DONE Pack the lighting worklist, then test level scheduling #QS1ERH

Intent: remove the retained frontier's per-item list cell, light-region-site, and copied local-coordinate without prematurely extracting the general fixed-point algebra discussed in #BA9MCA. Represent a lighting site densely as a region entry plus domain offset, while iteration reconstructs only a dynamic-extent local value.

Evidence gathered:

The fixed seed-121 81-chunk solve produced these three-run ranges on SBCL 2.6.6:

FrontierSecondsConsedVisits
retained structs and consesabout 1.23264.1--264.2 MB2,430,167
packed LIFO1.245--1.26531.8 MB2,430,167
packed level buckets0.180--0.18113.3 MB340,050

Packing therefore removed about 232 MB without improving latency: the former LIFO order, not allocation alone, caused the repeated fixed-point work. Level scheduling reduced the solve to about 1.03 visits per resident cell and is now the default for both reference and incremental lighting. On the same 81-chunk world, initial incremental settlement took 0.240 seconds and 487,321 visits; placing one crystal then reconciled in 0.001 seconds and 1,213 visits, changing five chunks.

Done when: lighting frontiers allocate no coordinate structure or cons per item, the unchanged-order variant isolates that allocation win, the scheduling experiment records both visits and elapsed time, and tests establish identical published light fields. A reusable solver protocol still waits for a second real client as in #BA9MCA.

Done: direct LIFO-versus-level coverage compares every sky and block array; the incremental suite covers edits, removal, arrivals, departures, unknown and open boundaries, and randomized agreement with the reference solver. The complete make test path passes, and the standalone smoke render remains byte-identical at SHA-256 0c85d4550638418cb88d507c6541a49147aeeea5697175d13904942b65a689ec. The measured packed representation now supplies the first client for the dynamic columnar-buffer design in #Y0TPND; #LDP5UR keeps that storage extraction distinct from a generalized solver.

defun make-light-worklist light-reference.lisp:43
defunmake-light-worklist
&key
scheduling:lifo
field-definition

Make the reference solver's packed LIFO or level-scheduled worklist.

This machinery belongs to the test-only voxel-light oracle. Both modes retain ENTRY plus dense OFFSET and LEVEL lanes, never a cons or coordinate object per item. #QS1ERH #LDP5UR

check-typescheduling
member:lifo:level
let*
row-declaration
records:make-columnar-row-declarationlayout-definition
andfield-definition`
level.,field-definition
buckets
dotimes
level
lengthbuckets
setf
arefbucketslevel
make-light-worklist-bucket:capacity256:row-declarationrow-declaration
%make-light-worklist:schedulingscheduling:field-definitionfield-definition:bucketsbuckets

DONE Pay checked adjacency cost once per visited site #FGT96H

Intent: preserve checked public domain stepping while avoiding the same bounds and coordinate type validation for each of a trusted worklist site's six neighbors. The packed frontier already proves its entry and offset; adjacency should validate that site once, then keep primitive steps inside the checked scope.

Evidence: do-chunk-site-neighbors is the trusted-scope counterpart of do-chunk-window-neighbors. It validates the domain and the popped offset once, binds the shape's dimensions, the chunk coordinate, and the site's components as fixnums, and steps each direction with primitive floor arithmetic; no coordinate object is built and the window is consulted only at a crossing. The compiled frontier kernel (#716UN6) now emits it for every popped site, while step-chunk-domain-site, continue-chunk-window-site, and the checked neighbor macro keep their public validation for arbitrary callers. A test walks every site of a 4 by 3 by 5 chunk with both macros and requires identical offsets, crossings, materializations, and availabilities. Light fields and visits are unchanged against both oracles; on the nine-chunk little world the compiled solver's median fell from 20.3 ms to 6.1 ms against the legacy solver's 23.9 ms, and a batch of forty solves from 813 ms to 242 ms. The old profile's chunk-domain-offset entry is gone from the compiled kernel; the remaining sampled cost sits at chunk crossings, where the light region's locate-chunk-window-site still resolves a coordinate key at about 275 ns and 58 bytes per crossing, some ten thousand times per solve.

Done when: a hot neighborhood visit validates its source once without weakening step-chunk-domain-site or arbitrary callers, the crossing-only window invariant and light fields remain exact, and the same profile records whether the removed checks produce a material latency change. All hold.

defun compilation-lowering-environment frontier.lisp:1073

Emission. The generated loop is deliberately plain: raw bucket vectors and counters are bound once at entry and written back at exit; the source site's lanes and field values are bound once per pop; the popped site is validated once and its six relations are primitive fixnum steps (do-chunk-site-neighbors, #FGT96H); a target's lanes are the source's own for a local step and are borrowed from the crossing materialization otherwise. Nothing per relation is generic, checked twice, or allocated.

defuncompilation-lowering-environment
compilation&key
source-pt

Map every arithmetic parameter to the emitted form holding its value.

Eager fields are read once into a variable; lazy fields lower to their read form wherever the law mentions them.

append
loopforloweredin
compilation-fieldscompilation
forlazy=
frontier-field-binding-lazy-p
lowered-field-bindinglowered
whensource-pcollect
cons
lowered-field-source-parameterlowered
iflazy
field-read-formlowered:source'source-offset
lowered-field-source-variablelowered
collect
cons
lowered-field-target-parameterlowered
iflazy
field-read-formlowered:target'target-offset
lowered-field-target-variablelowered
mapcar
lambda
parameter
consparameter
lang:arithmetic-object-nameparameter
compilation-constantscompilation
whensource-p
mapcar
lambda
parameter
consparameter
lang:arithmetic-object-nameparameter
compilation-predicatescompilation
deftest trusted-site-neighbors-agree-with-checked-window-steps world-tests.lisp:448
deftesttrusted-site-neighbors-agree-with-checked-window-steps

do-chunk-site-neighbors validates a site once and steps primitively; it must expose exactly the offsets, crossings, and window results of the checked do-chunk-window-neighbors at every site of a small domain, consulting the window only at crossings. #FGT96H

let*
world
make-block-world:chunk-width4:chunk-height3:chunk-depth5
domain
block-chunk-domainchunk
declare
ignoreneighbor
let
agreements0
dotimes
let
checkednil
trustednil
let
do-chunk-window-neighbors
targetdestinationcrossingdirectionmaterializationavailabilityworlddomainlocal*voxel-face-directions*
valuesdestination
push
listtarget
andcrossingt
directionmaterializationavailability
checked
do-chunk-site-neighbors
targetcrossingdirectionmaterializationavailabilityworlddomainoffset*voxel-face-directions*
push
listtarget
andcrossingt
directionmaterializationavailability
trusted
when
equalcheckedtrusted
incfagreements
ok
ok
signals
do-chunk-site-neighbors
targetcrossingdirectionmaterializationavailabilityworlddomain60*voxel-face-directions*
valuestargetcrossingdirectionmaterializationavailability
'error
defmacro do-chunk-site-neighbors world.lisp:473
defmacrodo-chunk-site-neighbors
offsetcrossingdirectionmaterializationavailabilitywindowdomainsite-offsetdirections&optionalresult
&bodybody

Execute body for the DIRECTIONS neighboring the site at SITE-OFFSET.

This is the trusted-scope counterpart of do-chunk-window-neighbors for a site already proved to belong to DOMAIN, such as one popped from a packed frontier. DOMAIN and SITE-OFFSET are validated once; the shape's dimensions and the site's components are then bound as fixnums, and each direction is a primitive step with no coordinate object and no repeated bounds or type check. offset is the destination's dense offset, CROSSING the direction when the step leaves DOMAIN, and MATERIALIZATION with AVAILABILITY are selected through locate-chunk-window-site only for a crossing. Public stepping keeps its checks; this scope pays them once per visited site. #FGT96H

let
window-value
gensym"WINDOW"
domain-value
gensym"DOMAIN"
site
gensym"SITE"
shape
gensym"SHAPE"
width
gensym"WIDTH"
height
gensym"HEIGHT"
depth
gensym"DEPTH"
chunk
gensym"CHUNK"
chunk-x
gensym"CHUNK-X"
chunk-y
gensym"CHUNK-Y"
chunk-z
gensym"CHUNK-Z"
site-x
gensym"SITE-X"
site-y
gensym"SITE-Y"
site-z
gensym"SITE-Z"
crossing-x
gensym"CROSSING-X"
crossing-y
gensym"CROSSING-Y"
crossing-z
gensym"CROSSING-Z"
local-x
gensym"LOCAL-X"
local-y
gensym"LOCAL-Y"
local-z
gensym"LOCAL-Z"
local-offset
gensym"LOCAL-OFFSET"
`
let*
,window-value,window
,domain-value,domain
,site,site-offset
check-type,domain-valuechunk-domain
check-type,site
integer0#.most-positive-fixnum
let*
,shape
voxel-space-chunk-shape
chunk-domain-space,domain-value
,width
chunk-shape-width,shape
,height
chunk-shape-height,shape
,depth
chunk-shape-depth,shape
,chunk
chunk-domain-coordinate,domain-value
,chunk-x
chunk-coordinate-x,chunk
,chunk-y
chunk-coordinate-y,chunk
,chunk-z
chunk-coordinate-z,chunk
declare
type
integer1#.most-positive-fixnum
,width,height,depth
typefixnum,chunk-x,chunk-y,chunk-z
unless
error"Offset ~D is outside domain ~S.",site,domain-value
multiple-value-bind
declare
multiple-value-bind
declare
dolist
,direction,directions,result
multiple-value-bind
,crossing-x,local-x
floor
+,site-x
voxel-direction-dx,direction
,width
multiple-value-bind
,crossing-y,local-y
floor
+,site-y
voxel-direction-dy,direction
,height
multiple-value-bind
,crossing-z,local-z
floor
+,site-z
voxel-direction-dz,direction
,depth
declare
typefixnum,crossing-x,crossing-y,crossing-z,local-x,local-y,local-z
let
,local-offset
+,local-x
*,width
+,local-y
*,height,local-z
,crossing
and
or
/=0,crossing-x
/=0,crossing-y
/=0,crossing-z
,direction
declare
typefixnum,local-offset
multiple-value-bind
,materialization,offset,availability
if,crossing
locate-chunk-window-site,window-value
+
*,chunk-x,width
,site-x
voxel-direction-dx,direction
+
*,chunk-y,height
,site-y
voxel-direction-dy,direction
+
*,chunk-z,depth
,site-z
voxel-direction-dz,direction
valuesnil,local-offset:local
,@body

DONE Attach existing voxel field definitions #SIYGXO

Intent: describe the existing block-content, sky-light, and block-light columns without changing their representations or conflating field identity, per-site quantity, aggregate encoding, product state, and provenance.

Evidence: luv.world.fields owns a small voxel-field-definition which is also a backend-neutral represented-value declaration. A definition names its field and site kind, value type and optional quantity, unavailable-value semantics, legal value type, aggregate representation policy, source form, and identity token. :block-content remains a palette-u16 categorical field. :sky-light and :block-light share u8 level storage but carry distinct :sky-propagation-level and :block-propagation-level quantities; attempting to substitute their declarations fails even though their representations are equal.

Existing block-content-column and chunk-light-field objects retain the definition identities current when their arrays are materialized. The light-region-entry capture and immutable block-mesh-snapshot copy those identities with their bytes. materialized-field-current-p makes live redefinition visibly stale by object identity rather than silently reinterpreting old arrays. Product revision and boundary stability remain on their existing owners, and every per-site lane remains an unwrapped u16 or u8.

The subsequent percolation in #1RF48O makes the representation side equally explicit. materialized-field-representation reaches the aggregate storage from a field owner, while field-representation-domain reaches the finite site domain from that storage. These are deliberately separate from materialized-field-definition: meaning, representation, domain, and product lifecycle remain four inspectable relationships rather than one overloaded "field object."

The focused world and luvcraft tests exercise definition redefinition, categorical content, distinct light quantities, and propagation of definition identity through live light fields, atlas entries, and worker snapshots.