Domains, bundles, and materialized worlds
The apparent CLOS–SoA contradiction #D5M8BZ
The little block world began with CLOS because generic functions, inspection, class redefinition, and multiple dispatch are unusually pleasant tools for a live engine. A later interest in structure-of-arrays storage can sound like a retreat from that object-oriented view: if data belongs in columns, were the objects a mistake?
There is no contradiction once object identity and repeated numerical storage are placed at different levels. CLOS says how the engine's meaningful things relate and which protocols they support. SoA says how many values of one representation are arranged for traversal. A world, domain, chunk, bundle, field specification, block kind, generator, and solver can be objects while the values at ten thousand sites occupy compact specialized arrays.
The stronger claim is that a domain/bundle model is itself object-oriented. It chooses domains, fields, materializations, and policies as the natural objects rather than assuming every sample is an independently allocated object.
Mentioned in: A seam, not a specification, The seam is shared spatial meaning, not shared ownership
Dispatch on descriptions; compute on domains #J7A2KD
The intended division is not "CLOS outside, low-level code inside." It is more precise:
- CLOS objects describe worlds, domains, field meanings, representations, policies, algorithms, and presentable categorical values;
- generic dispatch selects an operation and storage representation once at an aggregate boundary;
- the selected operation borrows domain-sized specialized columns and runs a closed loop over offsets; and
- a cell normally contributes values to those columns without acquiring heap identity, ownership, or a lifecycle.
This makes the high level more semantic while leaving the numerical middle legible to the compiler, SIMD backends, profilers, and eventually GPU kernels. It also gives inspectors and interactive tools excellent CLOS objects to show without requiring the simulation to manufacture one such object for every site it visits.
The naming should reveal which view is being requested. In the current block
world, world-block-at is the single-site world-coordinate accessor: it
constructs coordinate descriptors, performs a resident-chunk lookup, and
decodes the palette value into a presentable shared object.
with-block-content-storage is the ordinary aggregate path: it dispatches
once for a chunk and borrows its domain, palette, and dense index vector.
Borrowed vectors are read-only unless a separate mutation protocol explicitly
owns revision and boundary invalidation.
The current dense mutation primitive is chunk-block-at-offset. It accepts a
site offset from the chunk's domain and updates the compact column without
constructing coordinates, but remains chunk-owned so content revision, exact
boundary revision, and world invalidation cannot be skipped. Terrain
materialization uses this route. Directly mutating a borrowed index vector is
not part of the protocol.
Neither API is shameful. A ray hit, inspector focus, editor selection, or error report really is row-shaped and benefits from description objects. A mesher, generator, lighting pass, fluid step, collision broad phase, or scientific field operation is domain-shaped; repeated calls to the descriptive API inside such a loop are an architectural smell even when their answers are correct.
Mentioned in: Draw populations are narrow columnar projections, Row meaning binds once; raw lanes run, A vocabulary is a finite domain, Vocabulary tables and population tables keep different time
Identity does not come from being addressable #C9L2WA
A cell can be addressed without being an individual in the engine's object model. It normally has no independent allocation, ownership, or lifecycle; its identity is the site's identity inside a domain. Its block content, light, moisture, or temperature are values borne at that site.
This suggests three useful views:
- a column exposes every value of one field for a whole-domain algorithm;
- a row exposes the several logical values at one site for inspection or local rules; and
- a focus combines a row with its domain position and a neighborhood access policy.
Rows and foci can be ephemeral CLOS objects without turning the stored world into an array of row objects. They are projections onto columnar storage.
Persistent objects remain appropriate where a thing really has a separate lifecycle: a player, a block entity, a resident chunk, an edit journal, or a mesh waiting for its GPU submission to retire.
Mentioned in: Things with identity and values at sites, The sparse–dense split is the row/column distinction with a forwarding address
What Moppe's Bundle demonstrates #E4N7PX
Moppe's current
spatial::Bundle is an eager, finite store of typed columns over one
domain. The durable storage contract is deliberately small:
- the domain supplies cardinality and checked index/offset conversion;
- every declared quantity specification occurs once;
- each field has one contiguous native vector;
- construction checks that every column has one value per domain site;
- a row proxy exposes one site without materializing a tuple; and
- an optional interpolation protocol belongs to domains that can define it.
Generic local operations live separately in
bundle_operations.hh. There a focus combines one row with its domain
index; the domain or an explicit policy supplies neighborhoods. This split is
important evidence: Bundle survived the deletion of a much broader terrain
expression/program system because finite typed storage and domain-owned
spatial meaning had independent value.
Moppe also keeps ground geometry, derived readings, and water in separate
bundles even though they share one terrain domain. The reason is ownership
and lifecycle, not layout. See
docs/surface-storage.md.
Mentioned in: A chunk domain owns local traversal, not world truth
A finite domain is more than dimensions #B8R3KF
At minimum a finite domain answers:
- how many sites exist;
- which index type names one site;
- how an index maps to a dense storage offset; and
- how a dense offset maps back to an index.
Additional capabilities should be explicit rather than presumed:
- a neighborhood relation;
- metric spacing or cell extent;
- continuous reconstruction or sampling;
- a boundary condition;
- a world-space placement; and
- a stable identity suitable for persistence or checked composition.
Moppe's periodic terrain domain can own its neighborhood because its torus is
closed. A voxel chunk is not closed: a face neighbor across an edge belongs
to another chunk. A local chunk-domain should therefore own local indexing
without pretending that an absent neighbor is intrinsically air. #H2W9DJ
separates local storage from cross-chunk access.
A domain's sites need not be places. A vocabulary — the atlas tiles, the block materials — answers the same four questions, and #SICRZO takes that seriously enough to ask who owns the mapping from a symbolic identity to the offset a hot loop indexes.
Different domains may cover the same region. A full-resolution block domain, a coarse occupancy domain, a body domain, and a contact-constraint domain have different sites and legal operations even when all ultimately describe one world.
Mentioned in: Viewport cells form a finite domain, Bodies, contacts, and constraints are domains of their own, A vocabulary is a finite domain
Bundle means a checked materialization #V6T1QS
A bundle is an eager answer to a finite question: for every site in this domain, what are the values of these fields? It is neither the entire world nor a universal database.
A useful tentative contract for luv is:
bundle domain closed schema of semantic field specifications one representation object or projection per logical field exactly domain-size logical values in every representation
The schema should be closed by a class or other inspectable definition. A hash table into which arbitrary properties can appear would lose the main benefit: an algorithm should be able to know which fields exist, what they mean, and how they are represented.
Whole-column algorithms may borrow storage directly. Row-oriented code may ask for a site view. Neither form should require copying the other.
Composition must check domain identity, not merely equal array lengths. Two 4096-element columns can describe different chunks, resolutions, placements, or kinds of site. Equal cardinality is not shared meaning.
Mentioned in: Cell presentation is a derived materialization, An index is closed by the vocabulary that interprets it, A save is a vocabulary and its columns
Logical fields and physical representations #L7C4GM
A semantic field and a storage lane are not the same thing. A field specification can name:
- its meaning;
- its logical value category;
- default or missing-value semantics;
- legal reconstruction or interpolation;
- a physical storage representation; and
- persistence and presentation conversions.
Several representations may implement the same logical field:
- a
constant-columnfor an all-air or uniformly lit region; - a
palette-columnfor shared CLOS block states; - a
dense-columnbacked by a specialized Lisp array; - a
nibble-columnfor compact four-bit light; - a
bit-columnfor a Boolean mask; and - a
split-vector-columnwhose one logical vector is represented by separate X, Y, and Z arrays for SIMD traversal.
Mutation may promote a constant representation to a palette or dense one.
The logical single-site description or position-at protocol need not change
when that happens, while aggregate operations can still select the new
representation once and traverse it directly.
This separation is where the Bundle and quantity ideas meet. Two fields can
share an (unsigned-byte 4) representation without becoming semantically
interchangeable. Conversely, one vector-valued position field can occupy
three scalar SIMD lanes without becoming three unrelated meanings.
The representation line is deliberately not "one field equals one array." A dense scalar field can use one array, but a constant field may store one value, a palette field needs a palette plus codes, a bit field packs many readings into a word, and one vector quantity may project through several SoA lanes. The checked materialization says how logical fields project into those physical representations; kernels borrow whichever raw pieces they require.
Mentioned in: Questions to keep open, One number participates in several kinds of meaning
Columnar layout, materialization, and policy are separate #Y0TPND
The packed lighting frontier in #QS1ERH is the first small dynamic relative of
a bundle. Its current sites are the ordinal domain [0, length); pushing and
popping changes membership, while three parallel arrays materialize one
entry, offset, and level reading for every member. The level buckets
decide which buffer receives or yields a row. That scheduling policy is not
part of the storage representation.
A reusable columnar substrate therefore has three parts:
layout physical lanes plus fixed and bound row meaning materialization one domain plus concrete storage satisfying that layout policy how membership and ownership change
A dynamic columnar-buffer combines the layout with a changing ordinal
domain and append/pop/reset policy. Its storage contract is:
- one logical length and capacity shared by every lane;
- one specialized vector per lane, with no independent fill pointers which can become skewed;
- coherent growth which allocates and copies every replacement vector before publishing any of them;
- append, truncate, reset, indexed access, and allocation-free row iteration;
- generated concrete operations, so a hot push or visit performs neither schema lookup nor generic dispatch; and
- retention policy per lane, so removing a row clears object references while bytes and floats need no ceremonial rewrite.
Push validates every incoming Common Lisp representation before modifying a lane, writes the row, and publishes the new length last. Reset clears the active prefix of reference-bearing lanes before setting the shared length to zero. A growth failure leaves the old vectors installed. These rules make the correspondence between columns an invariant of the abstraction rather than a convention repeated by each client.
Stacks, level buckets, FIFO rings, and eventually swap-dense solver sets can compose this storage, but they should remain separate policies. The first substrate need only earn the operations used by lighting. Stable identities, forwarding tables, and swap removal belong to the later body-domain client described by #Y5H8KC; fixed-point algebra still waits for its second client as in #BA9MCA.
A fixed materialization instead receives an explicit finite domain, asks its
domain-cardinality once, and allocates exact specialized lanes. It has no
capacity or independent membership mutation. Both policies retain the same
columnar-layout-definition and columnar-row-declaration vocabulary, so
semantic checks stay at the aggregate boundary while array loops remain raw.
Mentioned in: Draw populations are narrow columnar projections, Pack the lighting worklist, then test level scheduling, Vocabulary tables and population tables keep different time
Row meaning binds once; raw lanes run #327W2B
A columnar definition has two related descriptions which must not collapse into one.
The physical lane layout fixes lane names, Common Lisp element types, initial values, and reference-clearing policy. It is sufficiently static for a defining macro to generate a concrete structure, specialized arrays, and inline operations. The row declaration says what values those lanes mean. It consists of represented-value declarations and may group several physical lanes into one quantity projection. Both descriptions are inspectable at the aggregate boundary; neither accompanies an individual runtime value.
The existing arithmetic vocabulary already supplies most of the semantic
half. A scalar lane can carry a represented-value-declaration with a
representation type and optional quantity-specification. A
quantity-layout can say that three columns together are one
:world-position vector rather than three unrelated scalars. Lanes such as a
CLOS entry reference or a chunk-domain offset remain outside that layout:
an integer site key is not thereby a dimensionless measured quantity.
Some row declarations are fixed by the definition site. An awake-body buffer
might always group three single-float lanes as a world position in metres.
Others are bound by the owner of one materialization. The lighting buffer's
level lane is physically (unsigned-byte 8) in both cases, but a sky-light
frontier should retain the concrete :sky-light field declaration while a
block-light frontier retains :block-light. Binding either field checks that
its represented value fits the generated lane and records its exact quantity
specification and definition revision. It does not introduce a runtime
quantity variable or a tag on each byte.
This yields three deliberate checking boundaries:
- Defining the layout checks lane names, element types, defaults, retention, and the disjoint named projections of any fixed row declaration.
- Constructing a buffer checks owner-supplied declarations against its physical lanes and retains the resulting concrete row declaration.
- Binding a kernel checks its parameter declarations against that row once, then borrows the raw arrays for a closed loop.
Ordinary push, pop, and row iteration consequently enforce representation
types but perform no dimensional dispatch. Quantity correctness comes from
the declarations at the producer, materialization, and kernel boundaries,
just as CPU arithmetic lowering erases checked quantity construction before
executing ordinary numbers. An inspector can still show the buffer's schema,
concrete field bindings, length, capacity, and lanes.
A tentative definition site illustrates the division without settling the surface syntax:
Generated names are preferable to a runtime-configured universal container: SBCL can see the element types and inline array access, while the schema and its quantity judgments remain ordinary semantic objects. This is the same object-at-the-boundary, dense-data-inside seam as #J7A2KD, now applied to a dynamic row domain.
Mentioned in: One arithmetic medium, many clients
Referenced from code: Concrete per-lane declarations retained once by a columnar materialization. Rows remain raw array elements; this object carries their checked meaning at
the aggregate boundary. #327W2Bdefclass columnar-row-declaration columnar.lisp:99 ↗
DONE Extract a quantity-aware columnar buffer #LDP5UR
Intent: extract the synchronized specialized storage proven by the lighting
frontier into a generated columnar-buffer substrate, while making its
physical lanes and concrete row quantity declaration inspectable. Keep level
scheduling, spatial stepping, and fixed-point behavior in lighting.
Evidence: define-columnar-buffer now publishes an inspectable physical
definition and generates an ordinary structure, specialized lanes, one shared
length and capacity, coherent allocate-copy-publish growth, and inline push and
pop operations. Push checks every representation before writing and advances
the length last. Pop and reset clear only declared reference-bearing lanes.
The row macros expand into direct array access without constructing a row or
dispatching on a runtime schema.
A columnar-row-declaration binds concrete represented-value declarations to
those lanes once. Fixed named lane groups lower to the existing
quantity-layout vocabulary; a two-column test retains one :position vector
quantity. Dynamic bindings reject incompatible representations. The
lighting worklist binds its level lane to the retained :sky-light or
:block-light field definition, so equal u8 storage does not erase their
different quantity names or definition revisions.
The generated light-worklist-bucket replaces three adjustable vectors and
independent fill pointers. Level buckets and their brighter-first policy stay
in lighting. On SBCL 2.6.6, five warmed runs of the same seed-121 81-chunk
solve visited exactly 340,050 sites, took 0.177--0.181 seconds, and consed
13,126,672--13,137,168 bytes. This matches or slightly improves the recorded
0.180--0.181 seconds and about 13.3 MB before extraction.
Done when: the lighting buckets use one shared length and capacity over generated specialized lanes; growth is coherent; pop and reset clear retained entries; push, pop, and iteration allocate no row and dispatch on no schema; tests reject an incompatible represented declaration and distinguish concrete sky-light from block-light bindings; a small split-vector declaration proves that several columns can retain one vector quantity; the current lighting correctness suites pass; and the packed level profile shows no material regression. Do not add stable ids, swap removal, a generalized solver, or a schema metaclass in this iteration.
Done: all conditions above are satisfied without adding the deferred policies.
The full make test path passes, including strict Parinfer, 62 luv tests, 149
luvcraft tests, the wiki suite, and generated SPIR-V validation. The standalone
smoke render remains byte-identical at SHA-256
0c85d4550638418cb88d507c6541a49147aeeea5697175d13904942b65a689ec.
Mentioned in: Lower checked domains to native SIMD kernels, Pack the lighting worklist, then test level scheduling
Referenced from code: Define a concrete synchronized structure-of-arrays buffer. Each lane is (NAME INITIAL-ELEMENT : Each lane slot carries its precise specialized array type, so the generated push and every direct lane access store and load raw elements without boxing them. Growth is rare, and one inlined REPLACE per lane over precisely typed arrays makes SBCL's constraint propagation take minutes on a buffer with a few dozen lanes. Call REPLACE instead. 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 #LDP5URdefmacro define-columnar-buffer columnar.lisp:239 ↗
type type [:CLEAR-ON-REMOVE T]). Optional
:QUANTITIES on name-and-options groups named physical lanes into fixed
quantity projections. The generated MAKE-, -PUSH, -POP, and -RESET functions
operate on raw specialized arrays with one shared length and capacity. #LDP5URdefun make-light-worklist light-reference.lisp:43 ↗
DONE Bind fixed columnar materializations to finite domains #LC5ULQ
Intent: make the dynamic lighting buffer one storage policy over reusable columnar layout metadata, add a fixed domain-bound policy, and prove the split with the meshing snapshot rather than a synthetic universal bundle.
Evidence: columnar-layout-definition now describes physical lanes and row
meaning independently of storage policy. define-columnar-buffer still
generates the growing frontier operations, while
define-columnar-materialization generates an exact set of specialized lanes
whose extent comes from luv.domains:domain-cardinality. The matching borrow
macro binds domain, extent, row declaration, and selected precisely typed
arrays once for an ordinary scalar or SIMD kernel.
The one-cell meshing halo is now a concrete block-mesh-halo-domain and its
content code, sky level, and block level arrays are one generated fixed
materialization. Sky and block lanes retain the existing distinct quantity
definitions. The content u16 lane intentionally does not claim to be the
logical :block-content field: it is a code projected through the snapshot's
palette, demonstrating why a field cannot always equal one array. The
snapshot keeps its domain, palette, logical content definition, and columnar
storage together across the worker boundary.
Done when: dynamic buffers use domain-independent layout metadata; a minimal finite-domain cardinality protocol allocates fixed lanes; fixed borrowing retains exact array types and row meaning; the meshing halo is the second real client; sky and block definitions remain distinguishable; direct and snapshot meshes remain bit-identical; and the normal test and smoke paths pass.
Done: the cold make test path passes with strict Parinfer, 63 luv tests, 149
luvcraft tests, 14 wiki tests, and generated SPIR-V validation. The standalone
smoke render remains byte-identical at SHA-256
0c85d4550638418cb88d507c6541a49147aeeea5697175d13904942b65a689ec.
Mentioned in: The current program already contains four windows
DONE Percolate domain-bound representations through voxel storage #1RF48O
Intent: make the fixed materialization vocabulary describe the ordinary resident and captured voxel fields too, and transfer palette-backed block content as one representation rather than as an accidental pair of arrays.
Evidence: resident chunk-light-field objects and captured
light-region-entry objects now retain the same generated
voxel-light-columns representation over their respective chunk domains.
Sky and block levels remain separate u8 lanes with distinct quantity-aware
field declarations; revisions and boundary state remain on the owning derived
product rather than being forced into the columns.
Palette-derived propagation loss and emission are another fixed
materialization. Their explicit block-palette-domain has one site per
palette code, and their u8 lanes project the dimensional declarations on the
block-kind opacity and emission slots. Thus the solver performs CLOS
dispatch once per palette entry and then sees only dense typed lookup arrays.
block-content-column now binds its palette and u16 codes to the authoritative
chunk domain. Async production transfers that aggregate object; publication
preserves the arrays while rebinding them to the owner world's fresh resident
domain. materialized-field-representation and
field-representation-domain make this relationship inspectable without
claiming that every logical field is exactly one array.
Done when: resident and captured light use one representation type; palette properties retain their quantities over an explicit finite domain; block content cannot be constructed with a mismatched extent or palette code; async loading transfers the representation and preserves single-writer ownership; and the complete 149-test luvcraft suite passes.
Mentioned in: Attach existing voxel field definitions
NEXT Give dynamic columnar populations explicit compaction #34G2K8
The block-particle population is the next honest client. Its block reference, position, velocity, size, age, and lifetime naturally form quantity-aware SoA lanes, but its update removes arbitrary expired rows while preserving survivor order, and overflow discards an oldest prefix. The current generated dynamic buffer owns push, pop, and reset only.
Do not hide a second private length protocol in particles.lisp. First give
define-columnar-buffer checked truncate/prefix-discard and an in-place stable
compaction vocabulary that moves every lane together and clears removed
reference lanes. Then migrate rendering and integration to borrowed lanes,
decide whether the exported row-object accessor becomes a presentation view,
and test stable ordering, bounded bursts, expiry, and retained-object release.
Mentioned in: Draw populations are narrow columnar projections, Allocation: what the profile taught, and how not to be misled by it
A columnar buffer is not one hot sequence #IUTMUY
SBCL's extensible sequence protocol can make an active lane view behave like
an ordinary sequence. A small probe subclassed sequence, supplied
sb-sequence:length and sb-sequence:elt methods, and then worked with both
cl:length and sb-sequence:reduce. Such a view could be pleasant for
inspectors, generic algorithms, and REPL tools because it presents only the
active prefix while retaining the buffer's own capacity underneath.
It is not the hot representation of a columnar row. An element of the whole
buffer is a heterogeneous product, so elt would have to allocate a row,
return an aliasing cursor, or invent another tuple representation. Even the
homogeneous single-lane probe pays generic iterator and operation calls per
element: ten sums of 1,048,579 floats took 0.425--0.458 seconds, versus
0.006--0.007 seconds for a typed loop over the borrowed array. Specializing
reduce could recover that loop, but then the useful abstraction is the
kernel itself, not generic sequence iteration.
The production seams are therefore with-columnar-buffer-storage for a
changing ordinal domain and with-columnar-materialization-storage for a
fixed explicit domain. They bind the active extent, retained row declaration,
and selected lanes with precise simple-array types. Tooling may later layer
SB-SEQUENCE views over that boundary when a concrete client wants them;
scalar and SIMD kernels borrow the arrays directly. #VKLLPR records the first
native pack experiment.
Mentioned in: Lower checked domains to native SIMD kernels
Chunk boundaries need an environment #H2W9DJ
Meshing, ambient occlusion, lighting, fluids, and collision all inspect neighbors. At a chunk edge, the required value belongs to an environment larger than the chunk's finite storage.
Possible explicit contexts include:
- a
block-worldthat resolves world coordinates through resident chunks, procedural sources, and edits; - a
chunk-neighborhoodcapturing the center plus resident neighbors; - a one-cell halo snapshot made for a deterministic meshing job; or
- a boundary policy that distinguishes missing, loading, generated-air, and intentionally closed regions.
The domain should say which local site an offset denotes. The environment should say what exists beyond this materialization. Keeping those questions apart avoids making streaming state look like terrain truth.
A derived mesh or collider can record the source generations of the center and neighboring chunks it observed. A boundary edit then invalidates every artifact whose proof included the old generations.
Mentioned in: A finite domain is more than dimensions, The voxel grid is the static tree, and the overshoot rule keeps seams smooth, The block world is the static tree
Level of detail changes the schema as well as the spacing #O3P8NV
Level of detail is not always the same field sampled less frequently. Exact block content is categorical; averaging stone and air does not produce a half-stone block. A coarse domain may need fields such as:
- occupied volume or coverage;
- minimum and maximum surface elevation;
- dominant and secondary material;
- surface normal or cone of possible normals;
- conservative collision bounds; and
- geometric or radiometric approximation error.
Those are new semantic readings derived from a finer materialization or directly from the procedural source. A coarse bundle can therefore have a different schema while sharing a world-space extent.
This also suggests that procedural generation should answer domain-shaped requests. The generator need not construct the finest blocks for a distant coarse chunk if it can produce honest coarse readings directly.
Mentioned in: Chunk hierarchy and level of detail are related but not identical
Products follow ownership and lifecycle #A6X2RT
One universal voxel bundle would combine values with very different causes and invalidation rules. A more legible arrangement might eventually include:
- a mandatory block-content bundle;
- a derived or incrementally maintained light bundle;
- an optional fluid bundle;
- ecological or geological readings over their own domains;
- sparse block entities outside dense site storage; and
- chunk-level mesh, collider, navigation, and persistence products.
This list is illustrative. A field should not exist because a framework has a slot for it. It should appear when an operation produces or consumes it.
Narrow computations can return narrow products. The world owner may retain
them side by side, compose checked views, or materialize a wider persistence
record. Lisp aliasing and mutation make a literal port of Moppe's consuming
join operation unnecessary until a real owner wants it.
The proposed terminal viewport #RD8AEI is a compact non-voxel client of the same distinction: Ghostty remains the authoritative source, while the renderer retains a narrow cell-presentation materialization and derives separate glyph and decoration populations from it.
Mentioned in: Cell presentation is a derived materialization, Bodies, contacts, and constraints are domains of their own, Domain, materialization, and revision have different identities, Value changes and shape changes cost differently, A film is not a material
The first Lisp atelier should stay ordinary #R3F8YC
A small experiment should attach ordinary inspectable field definitions to the block-content and light columns which already exist, without committing the game to a general framework or changing their storage. The concrete spatial extraction and field/kernel bridge are developed in #18MPLA.
A later metaclass experiment might look like:
Such a metaclass could turn field slot definitions into inspectable schema
objects and allocate columns from the instance's domain. It could reject
duplicate field meanings and teach describe or an inspector how to show
both logical values and physical storage.
Common Lisp class redefinition would offer a particularly playful experiment:
update-instance-for-redefined-class could add a newly declared column to
already resident chunks with the proper domain-sized default. Whether that
remains delightful after real use is an empirical question. Start there only
after block content, voxel light, and another real field have shown that
class-level schema, allocation, and migration repeatedly want the same
machinery.
The hot path need not use generic dispatch per site. A generic operation can select a bundle representation or numerical phase once, borrow specialized arrays, and run an ordinary optimized loop. #R9M4PV applies that arrangement to physics and possible wide backends.
Mentioned in: Select the phase once, then run closed loops
What this page does not decide #N6Q2SH
- the chunk dimensions or world coordinate convention;
- whether schemas are metaclasses, macros, ordinary CLOS objects, or some smaller protocol;
- whether palette indices begin as bytes or a wider representation;
- whether light is packed immediately;
- the exact streaming state machine;
- whether level-of-detail domains form an octree, clipmap, or another hierarchy; or
- which physics data structure will emerge from the separate design work.
The first proofs are now present: the current world occupies finite domains with palette-backed block content, and voxel lighting publishes separate sky and blocklight columns with its own revisions. Their common indexing did not make one universal bundle desirable. Instead, the working light region, meshing neighborhood, and halo snapshot revealed repeated location, stepping, boundary, and offset machinery. #K3KZTG records the smaller next proof: extract that shared spatial vocabulary before introducing a schema metaclass or a generalized solver.
(name-and-options &body lane-descriptions)Define a concrete synchronized structure-of-arrays buffer. Each lane is (NAME INITIAL-ELEMENT :TYPE TYPE [:CLEAR-ON-REMOVE T]). Optional :QUANTITIES on NAME-AND-OPTIONS groups named physical lanes into fixed quantity projections. The generated MAKE-, -PUSH, -POP, and -RESET functions operate on raw specialized…
Logical disjunction of tests and raw truth values.
Concrete per-lane declarations retained once by a columnar materialization. Rows remain raw array elements; this object carries their checked meaning at the aggregate boundary. #327W2B
Semantic quantities packed into disjoint positions of one representation.
Headings, paragraphs, figures and their IDs, mentions, marks.
Test whether two compatible scalars are equal.
(name)Return the inspectable physical row layout named by NAME.
The physical lanes and fixed row meaning generated for a columnar type.
One generated column's name, element representation, and retention policy.
Multiplication and scalar scaling.
(&body body)The declarations BODY makes, read as a rule body, for a rule to include: the way to write a mixin.
Logical conjunction of tests and raw truth values.
One top-level defining form of a source file.
(definition &optional declarations)Bind concrete represented DECLARATIONS to DEFINITION's physical lanes. DECLARATIONS is an alist from lane names to represented-value declarations. Representation compatibility and duplicate semantic ownership are checked once; returned rows retain the concrete declarations without wrapping values.
(definition lane-name declaration reason)The maximum of compatible quantities.
(&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
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.
Meshing, ambient occlusion, lighting, fluids, and collision all inspect neighbors. At a chunk edge, the required value belongs to an environment larger than the chunk's finite storage. Possible explicit contexts include: – a block-world that resolves world coordinates through resident chunks, procedural sources, and…
#B8R3KF asks four questions of a finite domain: how many sites exist, which index names one site, how an index maps to a dense offset, and how an offset maps back. A voxel chunk answers them. So does a set of block materials, and so does a set of atlas tiles. The sites are not places; they are members of…
The domain/bundle vocabulary was developed for terrain, but its real claim is broader: a finite domain identifies sites, and fields have values over those sites (#B8R3KF). Box3D, read in this vocabulary, is a physics engine built from exactly such domains — with the twist that domain membership is dynamic: – The…
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,…
The intended division is not "CLOS outside, low-level code inside." It is more precise: – CLOS objects describe worlds, domains, field meanings, representations, policies, algorithms, and presentable categorical values; – generic dispatch selects an operation and storage representation once at an aggregate boundary;…
Intent: eventually let a field or simulation phase select a scalar or SB-SIMD realization of the same semantic operation once per dense kernel. Evidence: #B5L7VG and the native SIMD study describe the representation seam, and #LDP5UR now supplies generated quantity-aware SoA storage. The first executable probe uses…
The visible terminal grid is a small, honest client of luv's finite-domain vocabulary. A terminal-grid-domain can own the current column and row counts, answer domain-cardinality with their product, map (x,y) to a row-major offset, and map an offset back to a cell coordinate. Row-major order is not just convenient…
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…
The hot path of a physics step must not dispatch per site. The arrangement #R3F8YC anticipates — a generic operation selects a representation or phase once, borrows specialized arrays, and runs an ordinary optimized loop — maps directly onto how Box3D structures a step: prepare (gather from domains into per-step…
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…
A columnar definition has two related descriptions which must not collapse into one. The physical lane layout fixes lane names, Common Lisp element types, initial values, and reference-clearing policy. It is sufficiently static for a defining macro to generate a concrete structure, specialized arrays, and inline…
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…
Intent: extract the synchronized specialized storage proven by the lighting frontier into a generated columnar-buffer substrate, while making its physical lanes and concrete row quantity declaration inspectable. Keep level scheduling, spatial stepping, and fixed-point behavior in lighting. Evidence:…
FIELD-DEFINITION supplies LEVEL's concrete represented-value declaration.