luv

Workshop wiki

quantities-and-measurement.org

Quantities, dimensions, and interpretation

One number participates in several kinds of meaning #U4M7KD

Moppe's use of mp-units and the smaller Zig Étalon experiment both resist a habit that becomes costly in simulation code: treating every float as the same sort of thing. The point is not merely to attach metres or seconds to a number. Several independent questions are involved:

Keeping those questions separate is more useful than choosing one elaborate "unit type." It lets a logical field retain strong meaning while its values occupy plain specialized arrays suitable for SB-SIMD kernels. Luv's pinned SBCL now makes that a supported CPU path on both arm64/NEON and x86-64 rather than a hypothetical backend. It also explains why the domain/bundle design in #L7C4GM is relevant to quantities rather than an unrelated storage concern.

The principal source material is Moppe's units guide and quantity catalogue, together with Étalon's workshop account. Étalon is evidence about a small mechanism, not a proposal to replace mp-units or to translate its Zig surface literally into Lisp. The mp-units dimensionless-quantities guide, dimensionless-unit workshop, and ISQ implementation article provide the corresponding library rationale and executable examples.

Dimension describes lawful physical algebra #D8Q2LF

A dimension is a product of base dimensions raised to integer or rational powers. For the mechanics we presently care about, an exponent vector over length, duration, and mass already distinguishes familiar cases:

quantitydimension
length\mathsf{L}
duration\mathsf{T}
velocity\mathsf{L}\,\mathsf{T}^{-1}
acceleration\mathsf{L}\,\mathsf{T}^{-2}
mass\mathsf{M}
force\mathsf{M}\,\mathsf{L}\,\mathsf{T}^{-2}
impulse\mathsf{M}\,\mathsf{L}\,\mathsf{T}^{-1}

Multiplication, division, powers, differentiation, and integration transform dimensions mechanically. A compiler or Lisp protocol can establish that a force divided by a mass is an acceleration without knowing whether the force came from gravity, a spring, or a whimsical block.

Dimensional equality is necessary for addition, but it is not always sufficient. It cannot tell us that two equal-dimensional values answer different domain questions.

A specification carries semantic identity #S5V9CN

Moppe gives names to quantities such as airspeed, rate of climb, standing water depth, terrain elevation, and spatial coordinate. Airspeed and rate of climb are both speeds, but accidentally adding or substituting one for the other usually expresses a mistake. Water depth and grid spacing are both lengths, but using water depth as a finite-difference spacing is nonsense.

A quantity specification can therefore be thought of as at least:

\text{specification} \;=\; (\text{dimension},\ \text{semantic meaning},\ \text{tensor order})

Two specifications may have equal dimensions and equal representations while remaining unequal specifications. This is the same principle by which two bundle columns can both contain single-float without becoming exchangeable.

Dimensionless values make the distinction especially clear. A probability, a proportion, a control input, a noise sample, a light level, and an occupancy fraction may all have dimension one. That fact does not make their APIs interchangeable. "Dimensionless" means the physical dimensions cancelled; it does not mean "semantically anonymous scalar."

A unit is a conversion, not the meaning #N3R6WY

Metres, feet, seconds, and milliseconds are units in which quantities can be constructed, displayed, persisted, or exchanged. They do not say whether a length is elevation, water depth, cell spacing, or a wing span.

The engine can normally store one coherent representation for a specification and convert at explicit boundaries. That preserves pleasant authoring:

make-quantity'cell-spacing50'centimetre
make-quantity'player-height1.8'metre

without requiring every hot loop to carry a runtime unit tag. A block world may choose one metre per block today and later allow a world description to choose another scale. The categorical block index still is not itself a length; a domain metric maps index differences to spatial displacement.

Dimension one still has semantic units #RK56VG

The Moppe catalogue makes the separation unusually concrete. Control and noise signals, proportions, probabilities, cell and iteration counts, terrain slope, normals, flow fractions, persistence, sediment concentration, and influence fields all have dimension one. They deliberately remain different quantity specifications. Moppe normally expresses them in mp-units' one, but that does not erase their names.

mp-units also gives dimension-one quantities real unit structure. percent, per_mille, and parts_per_million are scaled versions of one. A quantity can change presentation from 50 percent to 1/2 one without changing from, say, opacity into probability. Other dimension-one units are narrower: radian belongs to angular measure, steradian to solid angular measure, and bit to storage capacity. Thus these are three separate questions:

questionanswers
quantity specificationopacity, probability, angle, storage capacity
dimensionone, length, duration, …
unitpercent, one, radian, bit, metre, …

Luv now models the first useful portion of this. Unit definitions carry a dimension, canonical basis, and magnitude. :percent is 1/100 of :one; :kilometre is 1000 of :metre. The source operation

convert-unit
quantity50.0:quantity:opacity:unit:percent
:unit:one

preserves :opacity and emits the numerical scale change. It cannot rename the value, and ordinary addition still requires exact units. The remaining mp-units lesson is unit admissibility: future definitions should be able to say that radians express angles but not arbitrary dimension-one values.

Points are not differences #P7A4HX

An origin-relative position is an affine point, while a displacement is a vector-like difference. The lawful operations are deliberately asymmetric:

\begin{aligned} \text{point} - \text{point} &\to \text{difference} \\ \text{point} + \text{difference} &\to \text{point} \\ \text{difference} + \text{difference} &\to \text{difference} \\ \text{point} + \text{point} &\to \text{not intrinsically meaningful} \end{aligned}

Terrain elevation, simulation time, and temperature-like scales can need the same distinction. Treating them as ordinary additive quantities silently chooses an origin and then permits operations that do not respect it.

Étalon's generated QuantityPoint and Quantity types make this distinction at compile time. In Lisp the important requirement is semantic, not that every value be boxed: a point-valued field may still occupy a raw array, while its field specification controls the operations made available on it.

Three affine characters: point, absolute, difference #LNRY72

The two-way split above is mp-units V2, and it is what affine-p in arithmetic/semantics.lisp implements today: a specification is either a point or a difference. mp-units V3's central discovery (#QFCPRA) is that this misses the commonest case. Most physical equations are written over absolutes: non-negative amounts measured from a true zero, on a ratio scale, forming a convex cone rather than a vector space. A mass, a duration, a distance-as-size are absolutes; so is nearly everything luvcraft's own vocabulary names.

The observation that makes this concrete for luv is in luvcraft/quantities.lisp. :opacity, :ambient-occlusion, :sky-light-level, :block-light-level, :material-emission, :fog-amount, :day-factor, :linear-rgb, :view-distance, :world-distance, :shadow-filter-radius are every one a non-negative amount from a physical zero. None is a signed difference, and none is a point. Under the two-way model they are all "differences" by default, so the system cannot say that a light level is non-negative, cannot distinguish "the difference between two light levels" (signed, a genuine difference) from "a light level" (cone), and cannot object to negating an opacity. Moppe's own catalogue — the system's primary source — marks proportion, probability, standing_water_depth, and sediment_volume as non_negative; that specifier was dropped in translation.

The V3 operation table, restated in luv's terms with affine-p generalized to a three-valued character:

Addition, by the character of the left and right operands:

+pointabsolutedifference
pointerrorpointpoint
absolutepointabsoluteabsolute
differencepointabsolutedifference

Subtraction, left minus right:

-pointabsolutedifference
pointdifferencepointpoint
absoluteerrordifferenceabsolute
differenceerrordifferencedifference

Multiplication and division:

operationresultexample
absolute \times absoluteabsoluteenergy = power \times time
absolute \times numberabsolute
absolute \times differencedifferencearea \times Δheight = Δvolume
difference \times numberdifference
absolute / absoluteabsolutea physical ratio: efficiency, strain
absolute / differencedifference
difference / absolutedifference
difference / differencedifferencevelocity = displacement / duration
point \times or / anythingerror
\lvert\cdot\rvert, norm, modulusabsoluteof a difference

Points keep the rules #P7A4HX already states. Absolutes and differences mix freely in addition because a signed difference shifting an amount does not destroy its zero anchor; the result stays absolute. Subtracting two absolutes yields a difference — the case that today collapses into "same thing as the operands" — and an absolute is recovered only by an explicit .absolute() promotion. A difference times an absolute is a difference: the type conservatively keeps signedness wherever it cannot prove otherwise. Vector quantities are always differences (there is no absolute vector); a norm takes a vector difference to a scalar absolute, which is exactly what luv's dot-derived magnitudes should be.

Non-negativity is an opt-in specifier on the quantity spec, inherited by named children of the same kind and checked at exactly three sites: construction of an absolute, .absolute() promotion, and absolute ± difference arithmetic. It is deliberately never derived from a defining equation: an equation captures dimension, not sign domain, and many signed quantities (thermal expansion coefficient, reactive power) have equations whose every factor is non-negative. Anonymous composed specs are therefore never non-negative; only a named quantity carrying the tag is. Whether negating a non-negative absolute is an error or an implicit demotion to a difference is an open question in the source; only "never an absolute" is settled.

What "same space" means for points is where the source is deeper than luv's proxy. Two points subtract only when they share an origin root: natural_point_origin (the quantity's own zero, with check_non_negative attached automatically when the spec is non-negative), a named absolute_point_origin (an isolated space — two such origins on the same quantity are incompatible), or relative_point_origin (a compile-time offset chain within one root, so Celsius, Fahrenheit, and Kelvin points all subtract through ice_point and absolute_zero). Conversions across independent roots need a registered frame_projection, always explicit and never inverted automatically — this is what handles axis-inverting maps such as altitude/depth and bearing/azimuth, and runtime-parameter maps such as world to camera frame. Notably, points on distinct natural axes do not mix even though their differences widen: height point minus width point is an error while height plus width differences give length. Luv's same-quantity-space-p requires equal names, which gets that rule for free; what it does not yet have is any notion of origin, so a :world-position point and a hypothetical camera-relative position would be the same space today. The semantic maps of #SM4DFB are luv's frame_projection: they already state domain and codomain spaces and refuse silent inversion.

The source also attaches range policies to origins — wrap_to_range for longitude and angles, reflect_in_range for latitude, clamp_to_range for sensor bands, the non-negative halfline — enforced at construction, unit conversion, arithmetic, and origin change. Texture coordinates with wrap or clamp addressing and periodic sun angles are the same shape; that is a later refinement, recorded here rather than folded into #SYUACW.

A second observation from the built system: affine-p is a property of a specification instance, set at use sites (:world-position :affine-p t in the ABI declarations), not of the quantity definition. The same :view-distance could be a point in one declaration and a difference in another. mp-units puts affine character on the spec and non_negative on the kind or spec (#MCUKKW). The character seems to belong on the quantity-definition as its default, with a use-site override only for the rare quantity that legitimately appears both ways — and non-negative as a definition-level specifier that the additive rules consult.

Restraint, as with the field axis: the two-way model has been sufficient for the shaders shipped so far because shading arithmetic rarely subtracts absolutes. The moment it does — a light-level delta for temporal filtering, an exposure difference, a signed AO adjustment — the missing case becomes a silent semantic hole rather than a checked error. #SYUACW is the probe.

define-quantity :light-level tests.lisp:409

The three-way affine character: point, absolute, difference. These are the executable claims of the V3 operation table in wiki figure #LNRY72.

math:define-quantity:light-level:kind:proportion:non-negative-pt

Constraints are marks the author places, not costs the compiler hides #PLRP3A

Three positions on the source's runtime machinery, taken so that #LNRY72 does not silently import mp-units' cost model into a zero-cost lowering:

defun interpret-quantity-specification semantics.lisp:1010
defuninterpret-quantity-specification
derivedinterpretation

Give a compatible anonymous derived specification an explicit meaning.

This is a semantic interpretation, never a numerical unit conversion. An already named quantity may only retain its name; anonymous derived results may acquire one when their dimension, exact unit, and tensor order agree with interpretation. Character must agree too, with one deliberate exception: a signed difference may be interpreted as an absolute. That is the explicit promotion the affine algebra otherwise never performs — the author asserts the amount is non-negative, and no lowering checks it (#PLRP3A). Points never cross to or from the other characters here.

unless
andderived
or
null
quantity-specification-namederived
eq
quantity-specification-namederived
quantity-specification-nameinterpretation
dimension=
quantity-specification-dimensionderived
quantity-specification-dimensioninterpretation
unit-expression=
quantity-specification-unitderived
quantity-specification-unitinterpretation
=
quantity-specification-tensor-orderderived
quantity-specification-tensor-orderinterpretation
let
from
quantity-specification-characterderived
to
quantity-specification-characterinterpretation
or
eqfromto
and
eqfrom:difference
eqto:absolute
quantity-operation-error'interpret
listderivedinterpretation
:incompatible-interpretation
interpretation

DONE Generalize affine-p to a three-valued character #SYUACW

Intent: replace the boolean affine-p on quantity-specification with a character of :point, :absolute, or :difference, add a :non-negative specifier at the definition level, and implement the V3 operation table (#LNRY72) in the arithmetic core — without changing any generated shader module.

Evidence:

Done when: satisfied — the algebra is in place and inert for the current vocabulary. Annotating luvcraft's amounts, which changes what production materials mean, is #9ZK7Q7.

DONE Declare luvcraft's amounts absolute and non-negative #9ZK7Q7

Intent: with the character algebra inert-by-default (#SYUACW), make the block-world vocabulary state its truth: :opacity, :ambient-occlusion, :sky-light-level, :block-light-level, :material-emission, :fog-amount, :day-factor, :linear-rgb, :view-distance, :world-distance, and :shadow-filter-radius are non-negative absolutes; :world-position, :shadow-uv, :texture-uv, :shadow-depth are points by definition rather than by per-site :affine-p t.

Evidence:

Done when: satisfied. The vocabulary owns the defaults, production materials compile under the stricter algebra without codegen drift, and invalid point and amount arithmetic has source-level regression tests.

Tensor order is independent of dimension #T2K8MJ

A speed may be a scalar magnitude or a velocity vector. A spatial coordinate may be a scalar coordinate along one domain axis or a vector position in space. Dimensions alone cannot distinguish those shapes.

Tensor order therefore belongs beside dimension and meaning in a specification. It prevents interpreting a scalar finite-difference result as a vector velocity merely because their dimensions match. It also leaves the physical representation open: one vector-valued field can be stored as interleaved triples, three split arrays, SIMD packs, or a temporary register tuple without changing its logical specification.

Rotation deserves care rather than a forced analogy. An orientation may be a quaternion or other group element; angular displacement and angular velocity have their own lawful operations. The quantity vocabulary should compose with such structures rather than make every mathematical object look like a scalar with a unit.

Representation vectors can carry semantic products #VC7VFY

A GPU vector is a representation shape, not necessarily one mathematical vector. Similar vector registers can have different semantic structure:

registerlanes
vec3 world directionxyz one homogeneous vector quantity
vec3 packed surface inputxy texture sample point; z ambient occlusion
vec4 texture resultrgb linear colour; a opacity

The second and third values are semantic products: operational packing puts several related values in one register without claiming that arbitrary lane combinations form quantities. A quantity-layout records disjoint exact projections from representation lanes to quantity specifications. Swizzling xy from the surface input therefore recovers the declared texture point, while z recovers ambient occlusion. Swizzling yz is an error because that projection has no declared meaning. Arithmetic on the packed vec3 itself is also an error; source must project a semantic value first.

Homogeneous quantities use a different rule. A named vector quantity may declare ordered :components in its define-quantity form. The definition owns that data and defines the component quantities in the same semantic kind. Its full xyz projection remains the original vector quantity, while x becomes an explicitly named scalar-axis quantity carrying the same dimension, exact unit, and affine character. No fallback silently calls one component the whole vector.

This learns from mp-units without confusing the two cases. Its vector_components protocol lets a genuine vector quantity opt into compile-time component decomposition, and its decomposition guide shows get<Idx> returning axis-specific quantity specifications in the same unit. The compile-time index is important because different axes can have different types. Luv adopts that explicit projection principle, then extends it to heterogeneous GPU packing which should not be modelled as one vector quantity at all.

The layout remains semantic metadata. It neither changes shader-type nor adds instructions. Interface declarations, uniform members, and texture sample results may publish layouts; references and samples carry them until an exact swizzle projects a quantity.

Calculation derives; interpretation names #I9F3QB

Étalon's strongest small idea is the separation between a mechanical result and a domain interpretation. Its Laplacian derives a specification from the field and coordinate specifications. Applying it to terrain elevation over a spatial coordinate yields the expected inverse-length dimension, but the operation does not simply declare that every inverse length is terrain curvature. A separate checked interpretation gives that result its named meaning.

This gives an attractive rhythm:

  1. Arithmetic and field operators derive dimensions and tensor shape.
  2. Equal meanings permit intrinsically meaningful addition or comparison.
  3. A named domain operation explicitly interprets a compatible derived result.
  4. Tests reject equal-dimensional but semantically mistaken substitutions.

The distinction now governs the shader arithmetic graph as well as motivating future CPU Lisp. Three zero-codegen source forms make the provenance of meaning explicit:

interpret deliberately rejects raw values. That forces source to reveal whether meaning came from construction, a declaration, an explicit assumption, or calculation. It remains a semantic interpretation rather than numerical unit conversion; exact-unit conversion is separate work #7MHTYL.

Bundles keep meaning outside the lanes #B5L7VG

Moppe stores quantities in typed Bundle columns. Étalon's Bundle(Domain, Row) experiment used Zig's MultiArrayList to prove a related point: a row schema can retain semantic quantity specifications while the physical data is columnar. It rejected a raw f64 field in the bundle schema even though the underlying representations were numbers.

For Lisp numerical kernels, this suggests that semantic type information usually belongs to the field specification and checked operation boundary, not in a heap wrapper around every scalar. A field can have:

The boundary performs the semantic check; the kernel does not redispatch on every lane. #R9M4PV follows this into the planned physics layout.

Declarations keep representation and quantity parallel #OXBSAY

A Common Lisp type and a quantity specification answer different questions. The type says which values and storage the implementation sees: vec3, double-float, a specialized array, or a shader :vec3. The quantity says what the represented value means: world position, velocity, duration, or linear colour. Neither should be encoded as a lossy alias for the other.

This is now one backend-neutral protocol in luv.arithmetic. A declaration publishes its representation type, homogeneous quantity specification, optional heterogeneous quantity layout, and source form. Arithmetic parameters implement it while deliberately leaving representation open; shader interface values implement it with a concrete shader type; and represented-value-declaration is the ordinary object available to CPU storage schemas. All three use the same quantity-declaration parser.

The declaration is not a wrapper around runtime values and does not make typep distinguish two equal representations. It is the inspectable side of a later boundary check: compare the storage declaration with an arithmetic parameter once, choose a compatible realization, and then execute over raw values. This gives #ADR040 a protocol without moving dispatch into its inner loop, and gives #P2KN1D the same pair of parallel judgments on the CPU that the shader graph already has.

defclass represented-value-declaration declarations.lisp:48
defclassrepresented-value-declaration
representation-type:initarg:representation-type:initformnil:readerdeclaration-representation-type
quantity-specification:initarg:quantity-specification:initformnil:readerdeclaration-quantity-specification
quantity-layout:initarg:quantity-layout:initformnil:readerdeclaration-quantity-layout
source-form:initarg:source-form:initformnil:readerdeclaration-source-form
:documentation

One represented value's machine form and optional semantic meaning.

The representation type and quantity judgment are deliberately parallel: two declarations may both use vec3 while denoting different quantities, and one quantity may acquire different representations in different backends. #OXBSAY

defclass quantity-class records.lisp:52
defclassquantity-class
closer-mop:standard-class
:documentation

A standard class whose annotated slots retain quantity declarations.

Slot access and instance representation remain ordinary CLOS. The metaclass only makes definition-time meaning inspectable and inheritable. #OXBSAY

DONE Declare quantities on Lisp storage #FLRFU8

Intent: let CLOS slots and defstruct slots state quantity meaning beside their ordinary Common Lisp :type, using the declaration protocol in #OXBSAY. Use the MOP where a CLOS class definition genuinely carries the metadata, but do not make it the only representation of the schema.

Evidence to gather:

Result:

Done when: satisfied. #GZ53LD now connects these declarations to checked CPU arithmetic at an explicit boundary.

defmacro define-quantity-struct records.lisp:228
defmacrodefine-quantity-struct
name-and-options&bodyslot-descriptions

Define an ordinary structure whose annotated slots publish quantity meaning.

The runtime representation and accessors are still those of DEFSTRUCT. #FLRFU8

let
name
if
symbolpname-and-options
name-and-options
firstname-and-options
ordinary-slotsnil
semantic-slotsnil
dolist
descriptionslot-descriptions
multiple-value-bind
ordinarysemantic
split-quantity-structure-slotdescription
pushordinaryordinary-slots
whensemantic
pushsemanticsemantic-slots
setfordinary-slots
nreverseordinary-slots
semantic-slots
nreversesemantic-slots
`
progn
defstruct,name-and-options,@ordinary-slots
defmethodstructure-declaration-for
record-name
eql',name
declare
ignorerecord-name
load-time-value
make-instance'structure-declaration:name',name:slot-declarations
list,@
loopforslotinsemantic-slotscollect`
make-instance'structure-slot-declaration:record-name',name:slot-name',
getfslot:name
:representation-type',
getfslot:representation-type
:quantity-specification:source-form',
getfslot:source-form
',name

DONE Check storage declarations against Lisp arithmetic #GZ53LD

Intent: make a declared CLOS or structure field usable as an input or output of one checked arithmetic definition without wrapping each runtime value. The boundary should compare the stored representation and quantity declaration with the arithmetic parameter once, choose the existing Lisp realization, and then call ordinary compiled code over the raw vec3 or scalar.

Evidence to gather:

Result:

Done when: satisfied. #AVRNHW now broadens the declarations beyond the first player and sky records before that review.

defun ensure-declarations-compatible declarations.lisp:99
defunensure-declarations-compatible
actualexpected

Require actual storage to satisfy expected represented-value meaning.

Quantity specifications and layouts agree exactly. A NIL expected representation leaves representation choice open; otherwise actual's Common Lisp type must be a known subtype. Return actual on success. #GZ53LD

flet
fail
reason
error'declaration-compatibility-error:actualactual:expectedexpected:reasonreason
let
whenexpected-type
unlessactual-type
fail:missing-representation-type
multiple-value-bind
subtype-pknown-p
subtypepactual-typeexpected-type
unless
andknown-psubtype-p
fail:incompatible-representation-types
let
actual-specification
expected-specification
unless
or
and
nullactual-specification
nullexpected-specification
andactual-specificationexpected-specification
quantity-specification=actual-specificationexpected-specification
fail:incompatible-quantity-specifications
let
actual-layout
expected-layout
unless
or
and
nullactual-layout
nullexpected-layout
andactual-layoutexpected-layout
quantity-layout=actual-layoutexpected-layout
fail:incompatible-quantity-layouts
actual
defun predict-player-position simulation.lisp:324
defunpredict-player-position
playerseconds

Predict unobstructed motion through the checked storage boundary. #GZ53LD

check-typeplayerblock-world-player
check-typesecondsdouble-float
funcall*predict-player-position-function*
player-positionplayer
player-velocityplayer
seconds

DONE Audit quantity-bearing Lisp storage #AVRNHW

Intent: inventory luvcraft's classes, structures, arrays, constants, and function boundaries which carry physical or domain quantities, then migrate the semantic owners in coherent groups. The audit must preserve distinctions between continuous quantities, categorical voxel coordinates, counts, IDs, and packed representation lanes; comprehensiveness does not mean attaching a unit to every number.

Evidence to gather:

Done when: every quantity-bearing luvcraft owner is either declared, mapped to an explicit boundary, or listed with a concrete unresolved semantic question; the migration remains cold-loadable and the full suite passes.

Audit so far:

The interleaved block-mesh vertex vector now carries a repeated 12-lane product declaration (#Q7M4PF), checked against the live block-surface vertex shader contract. Context-dependent removal work now has an explicit semantic owner (#K2V9RD). The fixed frame uniform now has an independently stated CPU product checked against the shader's parsed member quantities (#F8U2LA). The remaining fullscreen and overlay vertex products are similarly declared and checked (#N6S2CX). The completed function-boundary audit is #J4V8QM: it found no consumer for a second metadata-only function signature system.

DONE Describe the repeated block-mesh vertex product #Q7M4PF

One block-mesh retains the declaration identity current when its flat single-float vector is constructed. The declaration repeats a fixed product at stride 12: cell-valued world position, texture UV, ambient occlusion, world direction, normalized sky and block light, and material emission. Construction checks the physical vector representation and exact vertex-count * stride lane count; combination preserves one declaration identity rather than silently relabeling copied lanes. A luvcraft test flattens the actual four location-ordered vertex-stage inputs and requires its quantity layout to equal the CPU product.

DONE Put contextual light-removal meaning on its queue #K2V9RD

The two removal frontiers are now small light-removal-queue structures. Each retains the exact :sky-light or :block-light field definition, dense field reader, sky propagation policy, raw item list, and removed-cell set. Items are admitted only through the queue, so their unwrapped u8 level is never created without its semantic owner; no tag or wrapper is added to each item. The item's coordinate slot independently declares a cell-valued affine world position. Tests require the two queues to expose incompatible light quantities, retain current field identities, reject an out-of-domain level, and leave the ordinary coordinate and integer representations unchanged.

DONE Check the packed frame product across CPU and shader #F8U2LA

camera-uniform-data now publishes the quantity layout of its fixed 20-float prefix, and frame-uniform-data publishes the full 72-float product. These remain ordinary specialized arrays: their declarations describe the cell-valued camera point, basis directions, projection and fog distances, sun and colour values, and shadow controls without wrapping any lane.

The host declaration is independent of *frame-uniform-members*. At buffer construction, a narrow luvcraft ABI adapter flattens the shader's actual byte-offset members and parsed component quantities into float positions; the host and shader layouts must compare equal in addition to occupying the same 288 bytes. The final four vec4 rows intentionally remain representation for the separately declared :world-to-light semantic map, rather than being mislabelled as sixteen homogeneous quantities.

DONE Declare the fullscreen and overlay vertex products #N6S2CX

The fullscreen sky triangle is a raw nine-float vector repeating one :clip-coordinate triple. The crosshair repeats six-float products of clip position and absolute linear RGB. Their constructors check representation, stride, vertex count, and semantic equality with the actual location-ordered shader inputs before either vector reaches a GPU buffer.

This comparison exposed a pre-existing declaration error in the larger frame product: CPU sky colours were correctly absolute linear-RGB signals, while their shader uniform projections had defaulted to difference character. The uniform members and independent CPU product now agree on absolute character; surface and sky arithmetic explicitly interpret their completed non-negative colour contributions as absolute linear radiance before addition and fog interpolation. The cold shader build therefore checks this character all the way through, rather than merely comparing interface declarations. Packing an absolute RGB signal with scalar alpha is an explicit representation boundary followed by an explicit assumption of absolute linear RGBA; it is not called an interpretation because the packed constructor deliberately has no derived homogeneous semantics. The DSL does not falsely treat that heterogeneous constructor as homogeneous arithmetic.

DONE Keep quantity contracts at owned function boundaries #J4V8QM

The source inventory distinguishes a function which owns a numerical law from one which merely orchestrates already-owned values. The former belongs in define-lisp-arithmetic-function and can bind its raw realization to slot, structure, constant, or ABI declarations. The latter stays an ordinary defun: a parallel quantity lambda-list would be descriptive metadata with no checker or lowering consumer.

The concrete boundary map is:

DomainDeclared owners and boundariesDeliberately ordinary values
camera and simulationcamera/player slots; camera ABI product; checked velocity-times-duration kernel; declared clip distances, vertical FOV, physics step, collision clearance, step height, terminal fall velocity, and maximum frame durationinput state, collision control flow, pixel extents, lattice cell indices
skyclock and keyframe slots; evaluated frame structure; frame uniform productinterpolation control flow and cyclic mod after its inputs are owned
targeting and world traversalcamera position slot; camera-basis direction at the checked camera ABI; declared application reach; ray-hit distance slotDDA cells, steps, infinities, and occupancy control flow
lightingfield definitions; context-owned removal queues; material slots; reconciliation duration0--15 field-domain codes, revisions, counters, and work tickets
meshing and renderingrepeated mesh, sky, and crosshair products; fixed camera/frame products; shader parameter and expression declarations; shadow constantsvertex/count/stride indices, texture dimensions, and raw matrix rows owned by the semantic map
streaming, production, and measurementproduction and lighting durations; frame-stage and benchmark structureschunk radii/counts, priorities, revisions, timeouts used only as operational API controls
persistence and captureserialized fields are the portable representation of declared world extent, camera look, and player position owners; orbit day-start, day-step, yaw, and forward step are explicit tool-input boundariesformat versions, image extents, pixel indices, and diagnostic difference scale

This pass removed the arbitrary eight-cell default from the reusable DDA: raycast-block-world now requires its caller to supply a maximum distance, while luvcraft-session-target owns the declared eight-cell player reach. Previously anonymous camera projection, frame-clamp, and controller limits are also inspectable quantity constants. Their values remain raw Common Lisp numbers; the controller and ray loop remain direct code.

The audit also classifies world-coordinate, chunk-coordinate, local-coordinate, voxel-direction, chunk shapes, terrain heights, texture pixel dimensions, IDs, revisions, tickets, and draw/vertex counts as lattice addresses, categorical displacements, indices, or counts. They must not gain continuous quantity units merely because their representation is numeric.

The durable SLY image accepted the new definition and constant declarations. Strict Parinfer, shader generation plus spirv-val, all 54 luvcraft tests, and the complete cold project/wiki suite pass. The next phase is the exercised design review, not a metadata frontend invented in anticipation of a use.

DONE Review the exercised quantity architecture #EYCANR

The review happened after the declaration protocol had real CLOS slots, structures, constants, dense fields, packed CPU/GPU products, one Lisp arithmetic kernel, and all production shader materials as clients. It keeps the central split: Common Lisp and GPU types describe representation; quantities describe meaning; layouts describe heterogeneous or repeated products; arithmetic graphs own numerical laws. None is a substitute for another.

Three tempting expansions are deliberately rejected by the exercised code. There is no quantity wrapper around every value, no quantity lambda-list on ordinary orchestration functions, and no representation-specialized Lisp compiler yet. The first two have no consumer. The third has only one small vec3 kernel and no measured dispatch problem; its current generic operations are chosen per arithmetic node, never per vector component or for semantic checking. A native/SIMD realization should begin with a measured kernel, not with speculative lowering machinery.

The exercise did justify two changes. First, point/absolute/difference and non-negativity moved to quantity definitions, eliminating repeated ABI annotations and exposing real clip/shadow mistakes (#9ZK7Q7). Second, the sRGB audit separated transfer representation from quantity meaning (#NC1YZ4): sampled atlas RGB is linear because the texture format performs the decode; that fact is not an encoded-rgb arithmetic quantity.

The clip-origin failure also identifies the next missing concept without pretending it is solved. representation=/=assume-quantity remains a loud, inspectable escape where the sky treats zero-origin clip coordinates as displacements. A general identity-valued “point to difference” operator would be unsound until quantity definitions can name origins and maps can cross them. #PLRP3A records that future design. Likewise, the duplicated homogeneous shadow projection was real work (#ZYCSYZ), while generic layout translation and more metaclasses were not earned.

Review result: keep the architecture, with definition-owned character and explicit texture-transfer metadata as the improvements learned from use. Revisit representation-specialized Lisp lowering only with profiling evidence, and revisit point-origin operations together with origin identity rather than as a zero-cost convenience operator.

The follow-up exercise confirmed those boundaries. #D1R2NK put the fog law in the backend-neutral luvcraft arithmetic system and realized it as both an ordinary compiled Lisp function bound to real sky slots and an inlined production shader call. #ZYCSYZ stayed shader-specific: it split one :world-to-light semantic map into homogeneous application and sampling projection without inventing a general matrix object or changing either GPU binary. The architecture therefore survived new CPU/GPU and compositional clients; its two remaining open marks concern optional native SIMD and structural shader representation types, not missing quantity semantics.

A possible Lisp vocabulary #G4C2ZX

A small atelier might make the layers inspectable without designing a general symbolic algebra system:

defclassquantity-specification
name:initarg:name:readerspecification-name
dimension:initarg:dimension:readerspecification-dimension
tensor-order:initarg:tensor-order:readerspecification-tensor-order
affine-p:initarg:affine-p:readerspecification-affine-p
defgenericmultiply-specifications
leftright
defgenericinterpret-quantity
derivednamedvalue
defgenericmap-field-values
operationoutput&restinputs

Canonical exponent vectors could make dimensional equality and algebra cheap. Named specifications could be singleton CLOS objects, allowing ordinary inspection and class redefinition. Field representations would own raw specialized arrays. Generated or declared kernel methods could check their specifications once and then enter SB-SIMD code.

This is intentionally weaker than making every arithmetic expression on ordinary Lisp numbers globally quantity-aware. We should first learn where checks buy clarity: constructors and presentation, field combinators, simulation phase boundaries, and explicitly compiled kernels are promising places.

Arithmetic definitions outlive execution backends #SQC5JN

The quantity algebra in arithmetic/semantics.lisp is already independent of any execution backend, but the compiled language which presently exercises it is still named and owned as a shader language. Its literal, reference, call, binding, quantity-boundary, and conversion objects live beside shader stages, resources, texture operations, and SPIR-V lowering. CPU code can ask the quantity protocol whether an operation is lawful, but it cannot yet define, check, and execute the same Lisp-shaped arithmetic graph without loading the shader system.

The intended ownership has three layers:

flowchart TB
  A["quantity algebra<br/><small>dimensions, units, named meanings, affine character, layouts</small>"]
  B["compiled arithmetic definitions and expressions<br/><small>source forms, lexical bindings, operator contracts, semantic checking</small>"]
  C["Lisp/CPU realization<br/><small>raw scalar and vector data</small>"]
  D["shader realization<br/><small>stages, resources, SPIR-V</small>"]
  A --> B
  B --> C
  B --> D

A shared definition is the durable source-level object. Each realization may produce its own typed expression graph or compiled artifact: shader literals are currently single floats, while CPU simulation already has legitimate double-float and specialized-array representations. Sharing the definition must not force those representations to become identical.

The runtime rule remains #B5L7VG: check meaning at definition, compilation, or kernel boundaries, then execute over ordinary dense values. A Lisp backend should therefore lower a checked definition to an ordinary compiled function; it should not wrap every scalar in a quantity object or redispatch for every array lane. SPIR-V becomes one realization of this arithmetic language rather than the owner of the language itself.

Shader-only vocabulary remains shader-owned: stages, interfaces, resources, texture sampling, output assignment, ABI packing, and instruction provenance. Arithmetic expressions, quantity boundaries, numerical unit conversion, portable operator rules, and source abstractions can move beneath that envelope. Semantic maps need a similar split: domain and codomain meaning are backend-neutral, while four shader rows or a CPU matrix are realizations.

DONE Give luvcraft quantities backend-neutral ownership #BRYSBI

Intent: let CPU code name world position, view distance, direction, colour, fog, and related luvcraft quantities without loading their definitions as a side effect of the shader system.

Evidence:

Done when: luvcraft quantities are owned outside LUV.SPIR-V; the application and shader packages share them explicitly; focused and project tests retain their behavior.

DONE Move portable operator semantics below shaders #ODID1K

Intent: make the semantic contracts of ordinary numerical operations available to every arithmetic client before extracting the expression frontend.

Evidence:

Done when: portable operator symbols and quantity-derivation methods are owned below LUV.SPIR-V; arithmetic tests exercise them directly; shader source keeps the same symbol identity and diagnostics; generated shader modules are unchanged.

DONE Extract a compiled arithmetic frontend #VIZMU6

Intent: make one inspectable definition and expression vocabulary usable by both CPU and shader realizations while retaining the shader workbench's source provenance.

Evidence:

Done when: a backend-neutral system owns arithmetic definition and base expression objects, one-expression functions and lexical let*, quantity checking, and source reconstruction; the shader frontend extends or consumes that protocol rather than duplicating it; existing shader graphs remain inspectable and lower identically.

DONE Compile checked arithmetic to Lisp #FTQEQD

Intent: execute arithmetic definitions on the CPU over ordinary Lisp numbers and vectors after checking their semantic contracts once.

Evidence:

Done when: a Lisp realization lowers a checked definition to a compiled function without runtime quantity wrappers; tests prove numerical execution, compile-time rejection of a dimensional mistake, affine arithmetic, and an explicit unit conversion.

DONE Share one production computation between CPU and GPU #D1R2NK

Intent: make definition sharing answer a real engine question rather than only demonstrate two toy evaluators.

Evidence: fog shaping is a small scalar candidate; the :world-to-light map is a richer candidate already used by production shaders and useful to CPU tests and tools. The latter should follow the semantic-map split and the homogeneous-projection composition work in #ZYCSYZ rather than forcing its current shader representation into the common layer. The field/kernel seam in #ADR040 supplies another production-shaped test: light-level decoding or a named light-response law can connect one semantic field to CPU tooling and GPU presentation without asking the arithmetic graph to own light propagation.

The first shared law is luvcraft.arithmetic:fog-amount-at-view-distance. It owns the production near/far clamp and quadratic curve as one backend-neutral arithmetic definition over :view-distance, returning :fog-amount. The block-world vertex shader now calls that definition instead of spelling out the formula, and shader lowering inlines it into the same target-appropriate scalar operations.

The CPU realization in luvcraft/sky.lisp compiles the same definition to an ordinary single-float function. Definition-time binding checks its ABI against the actual fog-near and fog-far quantity slots on sky-frame-parameters; sky-fog-amount-at-distance then answers the engine question with raw Lisp floats and no runtime quantity wrappers. Tests find the named call in the production shader graph, execute the CPU curve below, between, and beyond its bounds, and reject substituting a :fog-amount slot where the shared :view-distance contract requires distance.

The definition lives in the LUVCRAFT.ARITHMETIC package rather than the quantity vocabulary or either backend: quantities name meaning, while arithmetic graphs own numerical laws.

Done when: satisfied by the named fog law and its two checked realizations. The richer :world-to-light composition subsequently became #ZYCSYZ without forcing homogeneous GPU representation into the common arithmetic layer.

IDEA Lower checked domains to native SIMD kernels #VKLLPR

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 with-columnar-buffer-storage to borrow two single-float position lanes with their retained row declaration. Separate scalar and SB-SIMD kernels compute the same squared-position reduction, with AVX/SSE selected once on x86-64 and NEON available on arm64; an odd row count exercises the scalar tail.

On SBCL 2.6.6 x86-64 with AVX2 available, five warmed pairs of 100 reductions over 1,048,579 rows took 0.060--0.061 seconds scalar and 0.008--0.009 seconds through eight-wide AVX, with zero steady-state consing. Disassembly shows two VMOVUPS loads, two VMULPS operations, and pack additions in the loop. This is real native execution over generated columnar lanes, not autovectorization.

The probe also exposed the semantic issue a real kernel must settle: the scalar result was 4.1941624e9 while the pack reduction was 4.1923423e9. Both are deterministic, but SIMD reassociation changes a long single-float sum. A future shared arithmetic kernel needs an explicit reduction-order or error contract rather than assuming the scalar oracle will be bit-identical. #IUTMUY records why SB-SEQUENCE is useful around this storage but not as its hot traversal protocol.

Done when: promoted when a real domain computation, rather than this storage oracle, owns a stable scalar definition, reduction contract, and columnar layout worth accelerating.

defun simd-test-position-energy columnar-simd-tests.lisp:100
defunsimd-test-position-energy
buffer

Run the native-width squared-position oracle with a scalar tail. #VKLLPR

#+x86-64
sb-simd:instruction-set-case
:avx
avx-test-position-energybuffer
:sse
sse-test-position-energybuffer
#+arm64 (sb-simd:instruction-set-case (:neon (neon-test-position-energy buffer)))#-
orx86-64arm64
(scalar-test-position-energy buffer)
defmacro with-columnar-buffer-storage columnar.lisp:515
defmacrowith-columnar-buffer-storage
bindingsbufferbuffer-type
&bodybody

Borrow BUFFER-TYPE's active extent, row declaration, and raw lane arrays.

bindings is (length ROW-DECLARATION (ARRAY LANE-NAME) ...). The buffer is evaluated once, and every array receives its precise specialized array type. This is the checked aggregate boundary for closed scalar or SIMD kernels; the kernel traverses the borrowed arrays without row objects. #VKLLPR

destructuring-bind
length-bindingrow-binding&restarray-bindings
bindings
let*
lanes
anddefinition
columnar-layout-definition-lanesdefinition
unlessdefinition
error"There is no columnar buffer definition named ~S."buffer-type
let
resolved-bindings
loopforbindinginarray-bindingscollect
destructuring-bind
variablelane-name
binding
let
lane
findlane-namelanes:key#'columnar-lane-definition-name:test#'eq
unlesslane
error"There is no ~S lane in ~S."lane-namebuffer-type
listvariablelane
let
buffer-value
gensym"BUFFER"
`
let
,buffer-value,buffer
let
,length-binding
,
columnar-generated-symbolbuffer-type"~A-LENGTH"buffer-type
,buffer-value
,row-binding
,
columnar-generated-symbolbuffer-type"~A-ROW-DECLARATION"buffer-type
,buffer-value
,@
loopforinresolved-bindingscollect`
,variable
,
columnar-generated-symbolbuffer-type"~A-~A-LANE"buffer-type
columnar-lane-definition-namelane
,buffer-value
declare
typefixnum,length-binding
,@
loopforinresolved-bindingscollect`
type
simple-array,
upgraded-array-element-type
,variable
,@body

DONE Prove semantic arithmetic in the shadow graph #9P358P

Intent: let one real shader calculation test whether dimensions, named quantity spaces, tensor order, and affine points belong in a reusable arithmetic graph. Keep SPIR-V resources and lowering shader-specific; keep semantic arithmetic in a backend-neutral layer that a later scalar or SIMD kernel could also consume.

Evidence:

Outcome: the production proof covers coherent named spaces, tensor order, affine points, exact units, source-facing failures, unchanged SPIR-V, and live presentation. Numerical unit conversion was deliberately not smuggled into interpret; the separate operation was subsequently completed in #7MHTYL.

DONE Give GPU components first-class quantity meaning #SQR8KR

Intent: make homogeneous vector quantities and heterogeneous packed GPU tuples different semantic structures. Let a whole vector already carry quantity meaning, let exact swizzles recover explicitly declared component meanings, and make every undeclared projection or packed-whole arithmetic operation fail at parse time.

Evidence:

Done when: achieved. Component meaning is declared at the value or resource boundary, projection is exact and checked, production shaders exercise both homogeneous and heterogeneous cases, and valid semantics remain representation- and codegen-neutral.

DONE Make numerical unit conversion explicit #7MHTYL

Intent: add a visible arithmetic operation for conversions such as kilometres to metres without weakening exact-unit checking or turning interpret into a scale-changing cast. Learn from mp-units' quantity/reference separation while keeping conversion policy small and inspectable.

Evidence:

Done when: achieved. Unit changes are numerical operations distinct from interpretation, and dimension-one units exercise the same mechanism as physical units.

DONE Constrain units to semantic quantity kinds #V3TSZA

Intent: add the mp-units distinction between universally scaled units such as one and percent and kind-specific units such as radian, steradian, or bit. Let semantic quantity definitions form a small open hierarchy without weakening the current exact-name safety.

Evidence: #RK56VG and Moppe's live quantity catalogue show why equal dimension does not imply either equal meaning or universal unit admissibility. mp-units uses dimension-one subkinds for angles and storage while retaining one and its scaled forms across the hierarchy.

Done when: achieved. Unit definitions declare an admissible quantity kind; quantity construction and conversion reject a dimensionally equal but semantically invalid unit; one and percent continue to express named dimension-one quantities; radians express angles but not opacity; and the design leaves room for symbolic magnitudes such as pi-over-180 without approximating them during semantic checking.

Semantic representation boundaries point both ways #F83KGZ

Semantic checking should not pretend an opaque numerical ABI understands quantities. The shader language therefore has two deliberately loud ways to cross that boundary. assume-quantity introduces meaning to a raw external value; representation exposes the raw machine value of an already semantic operand. Both remain first-class expression objects with source and lowering provenance, and both alias their operand without emitting an instruction.

representation is not a general escape from a type error: its operand must already carry checked quantity semantics. Above the node, arithmetic is raw until a later assume-quantity boundary introduces a stated meaning. This exposed the original block-shadow calculation honestly while its four rows were still an unmodelled matrix. The block-surface shader now replaces that particular escape with the semantic map in #SM4DFB; representation remains the right boundary where only an opaque ABI operation is actually known.

Maps carry spaces; rows carry representation #SM4DFB

A tensor quantity and a map answer related but different questions. A tensor quantity says that a scalar, vector, or higher-order representation carries a quantity specification. A map says what source space it accepts, what target space it produces, and what kind of application is lawful. Four vec4 rows alone cannot say whether they map world points or directions, whether their result is clip space or a packed sampling tuple, or whether homogeneous division and a viewport remap are part of the operation.

This is where luv deliberately extends the mp-units lesson. mp-units supplies tensor-character quantities, vector component decomposition, and tensor inner products; those are useful semantics for the values represented by vectors and tensors. Moppe's atelier/matrix.hh then shows the application boundary plainly: quantities are converted to raw values before entering a simd_float4x4. Luv controls the shader DSL and can retain the missing relation as an inspectable object instead:

define-projective-shader-map:world-to-light:domain-type:vec3:domain-quantity:world-position:domain-unit:metre:domain-affine-pt:sample-type:vec3:sample-components
:xy:quantity:shadow-uv:unit:one:affine-pt
:z:quantity:shadow-depth:unit:one:affine-pt
:coordinate-scale
1/21/21
:coordinate-offset
1/21/20

shader-map-definition is an open semantic protocol. The first concrete definition is projective rather than pretending perspective division is a linear operation. project-point requires the exact affine metre-valued domain and four raw represented rows, producing the homogeneous light-space clip value. project-sample then performs division and the declared remap; its virtual product exposes xy as affine shadow UV and z as affine shadow depth. The packed sample whole is not treated as a homogeneous mathematical vector.

Lowering expands the application into the existing homogeneous construction and four dot products. A direct consumer materializes the vec4; a sampling projection reuses the four components, performs division and remap, and lets exact swizzles select the three sample components without an intermediate vec3. Thus domain and point semantics become stricter while both production SPIR-V modules stay byte-identical. Future linear, affine, normal, and projective map definitions can share this separation: dense coefficients are representation; the named map owns meaning.

Where luv's semantic arithmetic goes beyond its sources #KXVP6Q

The research pages (#6PQKNU, #PBADKM, #C4XGYX) were written in parallel with the implementation rather than before it, so this figure records the comparison after the fact, in the source-study voice: which of the built mechanisms are adoptions, which are extensions with no counterpart in the sources, and which source ideas were considered and declined.

Two mechanisms have no counterpart in mp-units, moppe, or measures:

Two source ideas were considered against the built system and declined, recorded here so they are not relitigated by accident:

Two source ideas remain open rather than declined: the ratio-scale absolute abstraction (#LNRY72) and log-domain levels for exposure (#2K8GBI, waiting on the HDR path).

DONE Carry semantic units through the vertex and camera ABI #S00PRF

Intent: extend the production proof from the heavily annotated fragment and sky calculations into the block vertex boundary. World and camera positions should be cell-valued affine vectors; their subtraction should be a cell difference; camera basis vectors should be unit directions; fog near/far should be cell distances; and normalized mesh lanes should cross the vertex/fragment interface with the meanings they already have downstream.

Evidence:

Done when: achieved. Vertex camera arithmetic is checked in metres, dimensionless fog is derived rather than asserted, normalized mesh meanings cross the stage interface, opaque representation boundaries are explicit, a metre/one mix-up is rejected, and the emitted SPIR-V is unchanged.

DONE Introduce the world-to-light sampling map #U0AHTQ

Intent: replace the most consequential remaining raw arithmetic island with a first-class map whose domain, codomain, affine-point requirement, packed result, and projective coordinate remap are checked independently of its four-row GPU representation.

Evidence:

Done when: achieved. A real production matrix application is a checked semantic map, point versus raw/direction misuse is rejected, projected fields arrive with meaning rather than assumptions, tools can inspect the map object, and emitted GPU code is unchanged.

DONE Separate homogeneous projection from shadow sampling #ZYCSYZ

Intent: let one semantic world-to-light projective map serve both production uses of the four shadow rows. The shadow vertex stage needs the homogeneous clip result directly, while the block vertex stage additionally divides and remaps it into shadow sampling coordinates. Those should be named operations over one map rather than two unrelated arithmetic spellings.

The map is now :world-to-light. project-point checks its affine :world-position domain and four raw rows, then denotes the homogeneous light-space vec4. project-sample accepts only such an application and projects its declared virtual :shadow-uv plus :shadow-depth sample product. The map definition names the homogeneous and sample representation types and owns the sampling layout and coordinate remap separately.

The shadow vertex stage sends the homogeneous application directly to clip position. The block vertex stage nests the same application under project-sample and selects exact sample fields; no intermediate vec3 or vec4 is materialized there. The McCLIM workbench presents both nodes. Tests inspect their relationship, require the same map definition in both production stages, reject raw/direction points and sampling projection of a raw clip value, and retain source provenance.

Done when: satisfied. Both Vulkan modules validate and remain byte-identical: block vertex SHA-256 9c99780b969b33eb242976c943463449b2cce4b66f779674a291fc3a4f2aeb34; shadow vertex SHA-256 4402a20bc62ef9289244ec0b52d2cd32f644147938d372eda308021ce49c300a. Direct MSL lowering mirrors the same composition and both production Metal sources compile.

DONE Give the remaining crosshair colour ABI meaning #NXA19H

Intent: finish the first production-shader audit at the small crosshair pipeline. Its literal vertex module carries an RGB ink tuple into the mathematical fragment method, where that boundary should state linear RGB and the final alpha construction should state opacity and linear RGBA explicitly.

Evidence: the mathematical crosshair vertex and fragment interfaces carry linear-rgb, the fragment constructs its fourth lane from an explicit opacity quantity, and the heterogeneous tuple crosses one visible representation=/=assume-quantity boundary into linear-rgba. The production test inspects all three meanings. The general quantity tests reject colour and proportion mixing, while #9ZK7Q7 adds the production amount failures. Both crosshair modules pass spirv-val and are byte-identical to 2bd3dfe (fragment d89e76bb…, vertex 2577a6d3…).

Done when: satisfied. Every crosshair colour lane has meaning, the packed RGBA boundary is explicit and checked, and lowering is unchanged.

An initial block-world quantity map #M8W6RP

The following is a vocabulary sketch rather than a frozen catalogue:

MeaningDimension or categoryShape / affine character
world positionLvector point
displacementLvector difference
cell spacing or extentLscalar or vector difference
cell index, palette indexdiscrete indexnot a physical length
durationTscalar difference
simulation instantTaffine point
linear velocityL T^-1vector
accelerationL T^-2vector
massMscalar
inverse massM^-1scalar
forceM L T^-2vector
impulseM L T^-1vector
orientationrotation structuregroup element
angular velocityT^-1 with angular meaningaxial vector
block content/statecategoricalpalette-valued
light levelsemantic dimensionlessscalar field
occupancy/coveragesemantic dimensionlessproportion field
probability, control, noisesemantic dimensionlessdistinct scalars

Counts, indices, and categorical codes should not acquire physical units just because they are represented by integers. Conversely, converting a chunk coordinate and local cell coordinate into a world position should pass through an explicit metric rather than an unrecorded multiplication by an assumed block size.

Rendering is radiometry: PBR as the forcing use case #T1GYOH

Physically based rendering is not merely a texture format; it is the claim that shading computes in radiometric quantities. Those quantities have a real dimensional algebra:

quantityunit
radiant flux\mathrm{W}
radiant intensity\mathrm{W\,sr^{-1}}
irradiance\mathrm{W\,m^{-2}}
radiance\mathrm{W\,sr^{-1}\,m^{-2}}
BRDF\mathrm{sr^{-1}}
solid angle\mathrm{sr}

The rendering equation is dimensionally typed:

L_o(\mathbf{x}, \omega_o) \;=\; L_e(\mathbf{x}, \omega_o) \;+\; \int_{\Omega} f_r(\mathbf{x}, \omega_i, \omega_o)\, L_i(\mathbf{x}, \omega_i)\, (\omega_i \cdot \mathbf{n})\, d\omega_i

Outgoing radiance is emitted radiance plus the integral of the BRDF (\mathrm{sr^{-1}}) times incoming radiance times a projection cosine over solid angle (\mathrm{sr}). The steradians cancel; radiance comes out. Energy conservation is an integral constraint on the BRDF that dimension-checking alone cannot prove, but dimension mismatch catches the commonest failures first.

The classic shader bugs are, almost without exception, quantity bugs:

Moppe's own units doctrine stops at the GPU: the atelier creed says numbers leave their units only through doors that "open only toward the GPU." Inside MSL every quantity is a bare float again, and the discipline reverts to comments and convention. That boundary is exactly where luv is differently positioned, because on our side of it the shader is still Lisp.

The frame uniform was an unlabeled quantity ledger #P6DF7B

This figure records the motivating condition as it stood before #SQR8KR and #S00PRF: it is kept as the why behind quantity layouts, not as a description of the present code. Today every frame-uniform lane declares its quantity through a quantity-layout (projection-vector is three :projection-scale lanes plus a :view-distance in cells; sun-color-vector is :linear-rgb plus a :sun-disc-coordinate), and the vertex ABI carries the same meanings.

At the time, the frame uniform's vec4 lanes were documented by comments only:

This was precisely the situation #S5V9CN describes on the CPU side: equal representation, distinct meanings, guarded by comment and habit. The uniform ABI was already a single declaration site shared between host writer and shader reader — declaring only representation, never meaning. The layout mechanism in #VC7VFY and #SQR8KR is what turned that one declaration site into a semantic one.

Dimensioned types in the shader DSL #TZHN8G

#I9F3QB anticipated that the derive/interpret rhythm could reach the shader compiler. PBR makes that concrete enough to design against. The shape that fits the existing compiler:

Restraint matters as much as the mechanism. Most texture lanes and intermediate swizzles are honestly anonymous; forcing a specification onto every value would reproduce the boxing mistake this page argues against on the CPU side. Unannotated values should flow as today, with specifications entering at declared inputs and named abstractions and propagating only where derivation is unambiguous. The first probe should be the place the checking pays immediately: the linear-light equation in the block material, the sRGB seam around the atlas sample, and the tonemap boundary when #WLHDRB lands.

Types with structure; judgments in parallel #P2KN1D

Two refinements sharpen #TZHN8G into a workable compiler design.

First, the representation types themselves should become structural before the quantity layer arrives. The current shader-type is already a CLOS class with component-count and image-metadata slots, but its identity is nominal: instances are interned under flat keywords, and :depth-texture-2d multiplies depth-ness and dimensionality into a name while the slots restate what the name encoded. SPIR-V is the evidence for the alternative — its own type grammar is compositional (OpTypeVector(element, count), OpTypeImage(sampled-type, dim, depth, arrayed, ms, format)) — so the flat keywords are a lossy projection of the IR lowering already emits. Type identity should be the structure, hash-consed so equality stays eq; keywords survive as parse-level abbreviations. New texture and vector flavors become parameter values, not registry entries.

Second, representation and quantity are two attribute systems walking one expression graph, not one entangled type system. mp-units must encode quantity algebra inside C++ templates because templates are the only compile-time computation C++ offers; a compiler we own has no such constraint. The representation judgment is total — every expression has one, and it is all SPIR-V or SBCL ever sees. The quantity judgment is deliberately partial — a triple of physical dimension, semantic kind, and scale/encoding (linear versus sRGB, radians versus degrees, raw 0..15 light levels versus normalized), where anonymous values propagate silently and only conflicts signal, with provenance. The rules genuinely differ per operator: a vec3 times a float is a vec3 representationally, while radiance times a proportion is radiance and radiance plus an encoded colour is an error. That asymmetry is why the systems must be parallel rather than merged.

One arithmetic medium, many clients #5N6SOQ

This figure is the design stance; #SQC5JN is the same idea as ownership layers, and #VIZMU6 / #FTQEQD are its realization as the :luv/arithmetic/language frontend and :luv/arithmetic/lisp backend. It is kept in the argumentative voice because the commitments below — language-owned semantics, driver-owned domain iteration, kernel-signature checking — are what the next marks (#D1R2NK, #VKLLPR) are measured against.

The typed expression DSL should compile to CPU Lisp functions as well as SPIR-V — and the right way to hold that is not "the shader language grows a CPU mirror" but the generalization of #B9M2FX: luv has a typed arithmetic language, and the block pipeline was merely its first client. The CPU is the second client in its own right. Pure CPU numerical code — physics kernels, lighting response curves, terrain and environment mathematics — writes in the same DSL because that is how CPU code obtains the same structural representation types and the same quantity judgment (#P2KN1D). One language, several backends; the GPU is one of them.

Three commitments follow from taking the language, rather than either backend, as primary.

The shared-definition payoff remains, now as one client among several. Even moppe's discipline could not prevent its CPU-side sky model from recomputing the daylight / golden formulas its shaders derive per-pixel; luvcraft's sky-frame-parameters has the same seam with the sky material. With one graph and multiple lowerings, "per-frame on the CPU into a uniform lane" versus "per-pixel on the GPU" is a placement decision about one definition. The atelier creed's bridge — "numbers only past the bridge" — stops being a wall and becomes a per-expression choice. Differential testing also survives as a corollary: any material can be evaluated at sample points on the CPU and compared with GPU captures, extending the reference/incremental discipline of #SKRELT from light propagation to shading.

Liveness comes free in both directions: shader definitions already rebuild pipelines through MOP dependents, and a compiled CPU closure is one more dependent of the same definition, updating transactionally beside the pipeline.

This position supersedes the runtime half of #G4C2ZX: rather than quantity-checked generic operations over runtime CL values, the DSL is the site of checking, and runtime CL only ever sees raw representations. Checking happens where compilation happens.

DONE Quantity-carrying shader types #7W7P72

Intent (as first written): extend the shader expression language's inferred types with an optional quantity-specification component, derived by operator methods and named at abstraction and resource boundaries, erased at lowering.

Evidence: this design was realized under other marks before this one was selected. #9P358P proves semantic arithmetic in the production shadow graph; #SQR8KR gives GPU components first-class meaning through quantity layouts; #S00PRF carries units through the vertex and camera ABI; and #FTQEQD shows the specification is erased at lowering on the Lisp side as it is in SPIR-V. The uniform member list now declares lane meanings that host writers and shader readers both see, and production shader modules stayed byte-identical while the checks were added. What this mark's own evidence list still lacks is the sRGB seam, split out below as #NC1YZ4.

Done when: satisfied by the marks above; retained so the design rationale in #TZHN8G and #P2KN1D stays traceable to its realization.

DONE Give the sRGB atlas seam explicit transfer meaning #NC1YZ4

The original intent correctly found a seam but proposed the wrong type. rgba8-unorm-srgb stores nonlinear RGB bytes; both Metal and Vulkan apply the sRGB transfer when the texture is sampled, so the value entering shader math is already linear. Alpha is ordinary normalized opacity. Labeling that sample encoded-rgb would make the quantity checker contradict the backend.

Transfer encoding is therefore representation metadata, parallel to rather than inside the quantity hierarchy. Texture resources may declare :sample-transfer :srgb-to-linear while their sample component layout says linear-rgb plus opacity. The block atlas does so. The host owns one +block-atlas-texture-format+, texture-format-sample-transfer maps its sRGB format to the same transfer, and session construction checks the actual descriptor format against the shader resource before creating the texture.

Tests inspect the decoded transfer and linear quantities, reject unknown transfers and transfer metadata on non-textures, and reject binding the block shader contract to rgba8-unorm. Sample-transfer metadata is erased from both SPIR-V and MSL lowering; the validated production binaries remain byte-identical.

Done when: satisfied by keeping encoded bytes at the texture representation boundary, declaring the automatic decode, and checking that declaration against the real host format. If luv later performs arithmetic over encoded CPU pixels, that separate client can earn an encoded-colour quantity; the shader sample cannot.

TODO Structural shader-type identity #RDI1HA

Intent: make shader-type identity structural — hash-consed composite types mirroring SPIR-V's own type grammar, keywords as parse abbreviations — per #P2KN1D. The CPU-lowering half this mark originally bundled is done: #FTQEQD compiles checked definitions to Lisp, and #D1R2NK is the NEXT mark for a real shared production computation. This mark keeps only the representation-type half.

Evidence to gather:

Done when: type identity is the structure, keywords are abbreviations, depth-ness is an image parameter, and existing shader tests and generated module hashes are unchanged.

Questions and executable claims #Q1D9ST

The first atelier should be judged by mistakes it prevents and hot paths it does not obstruct. Useful executable claims include:

Open design questions remain. Should specifications be instances, classes, symbols naming immutable definitions, or some combination? Which dimension algebra belongs at macro-expansion time and which remains useful live? How does a redefined specification invalidate compiled kernels and resident bundles? Can ordinary scalar reference code and a SIMD specialization share one semantic operation definition? A small real physics or lighting kernel will answer these better than a freestanding units framework.