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:
- What physical dimension does the value have?
- What domain meaning does this value carry?
- In which unit was it written or presented?
- Is it a quantity or an affine point?
- Is it scalar, vector, or another tensor order?
- Which numerical representation stores it?
- Over which domain is it a field?
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.
Mentioned in: What the package never had
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:
| quantity | dimension |
|---|---|
| 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:
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."
Mentioned in: What the package never had, Specifiers on the spec: one declaration site for meaning, The frame uniform was an unlabeled quantity ledger
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:
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.
Mentioned in: Rendering is radiometry: PBR as the forcing use case
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:
| question | answers |
|---|---|
| quantity specification | opacity, probability, angle, storage capacity |
| dimension | one, length, duration, … |
| unit | percent, 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
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.
Mentioned in: Constrain units to semantic quantity kinds
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:
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.
Mentioned in: Three abstractions: point, absolute, delta, Three affine characters: point, absolute, difference
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:
| + | point | absolute | difference |
|---|---|---|---|
| point | error | point | point |
| absolute | point | absolute | absolute |
| difference | point | absolute | difference |
Subtraction, left minus right:
| - | point | absolute | difference |
|---|---|---|---|
| point | difference | point | point |
| absolute | error | difference | absolute |
| difference | error | difference | difference |
Multiplication and division:
| operation | result | example |
|---|---|---|
| absolute \times absolute | absolute | energy = power \times time |
| absolute \times number | absolute | |
| absolute \times difference | difference | area \times Δheight = Δvolume |
| difference \times number | difference | |
| absolute / absolute | absolute | a physical ratio: efficiency, strain |
| absolute / difference | difference | |
| difference / absolute | difference | |
| difference / difference | difference | velocity = displacement / duration |
| point \times or / anything | error | |
| \lvert\cdot\rvert, norm, modulus | absolute | of 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.
Mentioned in: Constraints are marks the author places, not costs the compiler hides, Generalize affine-p to a three-valued character, Where luv's semantic arithmetic goes beyond its sources, A fluid dual system: code contributes to the wiki, The site shows code references to figures
Referenced from code:define-quantity :light-level tests.lisp:409 ↗
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:
- Non-negativity is a compile-time fact by default. A
non-negativespecifier participates in the character algebra — it decides what an absolute may become, and it is what a later interpretation may promise — but no lowering emits a check for it. Runtime enforcement, where wanted, is an explicit optional safe mode per definition or per compilation: a Lisp realization can signal; a shader realization has no exception path, so its safe mode would need an intentional channel such as a diagnostic output lane, an NaN/sentinel poison, or a debug-only clamp, and that channel is itself a design to record before assuming it. Silent per-operation clamps in production shaders are exactly the unreasoned checking this rule forbids. - Wrap and clamp are explicit source marks. The system should know
what a range policy means — that a wrapped coordinate is periodic, that
a clamped one saturates — so it can derive and check the result's
meaning, but the author says where the wrap or clamp happens. A
wraporclampboundary form in the arithmetic language, carrying its range and yielding a specification the checker understands, is the natural shape; texture addressing modes and periodic sun angles are the first clients. Range policies attached to origins and enforced at every mutation, as the source does, are not adopted. - Origins and frame projections are central, not peripheral. Working in the wrong coordinate space — world, view, clip, light/shadow, screen, texture — is the classic graphics bug, and it is a "same space" question the source answers with origin identity. The semantic maps of #SM4DFB are already luv's explicit crossings; giving point specifications an origin, so that two positions subtract only when their spaces agree and a map is the only way across, is the next design step after #SYUACW rather than part of it.
Mentioned in: Review the exercised quantity architecture, A fluid dual system: code contributes to the wiki, Code views: a dexp renderer for Common Lisp, The site shows code references to figures, Read luv sources with Eclector into a definition index
Referenced from code: Give a compatible anonymous 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
defun interpret-quantity-specification semantics.lisp:1010 ↗
derived specification an explicit meaning.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.
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:
quantity-specificationcarries acharacterslot;affine-psurvives as a reader (character is:point) and as a constructor keyword.quantity-definitiongainsnon-negative-pand a defaultcharacter;define-quantityaccepts:non-negative-p(which defaults the character to:absolute) and:character, and components inherit both. An unstated character falls to the definition's default, and every quantity inluvcraft/quantities.lispis unannotated, so every existing specification is a difference exactly as before.additive-pairimplements the three-way table;product-charactermakes products and quotients absolute only when every factor is; a bare number preserves the other operand's character;interpretpermits the one deliberate crossing, difference to absolute, and no other. Negating an absolute signals:cannot-negate-amount: luv chose the error branch of the source's open question so demotion is a visible mark.arithmetic/tests.lispgained six executable claims covering the definition defaults and the historical:affine-pspelling, cone addition and subtraction, point asymmetries, the multiplicative table, negation, and interpretation-as-promotion. The full fresh-process suite passes (18 arithmetic, 4 language, 5 lisp, 13 core, 31 spir-v, 34 luvcraft), and strict parinfer is clean.- The language and shader frontends pass
:affine-pand the new:characterkeyword only when stated, so definitions can supply defaults; adeclared-charactershim keeps every existing source form valid.make shader-validatein a fresh SBCL regenerated all seven production modules byte-identical to their pre-change hashes withspirv-valpassing, andmake smokeat HEAD gives MD5edc597168a3ec79a04c6d2af53c5b1a5both with and without the change (the earlier recorded226c4f…predates the shadow commits and was stale).
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.
Mentioned in: Three affine characters: point, absolute, difference, Constraints are marks the author places, not costs the compiler hides, Declare luvcraft's amounts absolute and non-negative
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:
world-position,shadow-uv,texture-uv,shadow-depth, andclip-coordinateare points at their definitions. The listed proportions, colour signals, distances, and filter radius are definition-level non-negative absolutes. Production slots, packed layouts, constants, and shader declarations now omit redundant character spellings; only real differences such as shadow texel size and receiver bias override a default.- Compiling the stricter production graph exposed rather than concealed four
distinctions: homogeneous clip XYZ/W is a heterogeneous representation;
clip coordinates must cross the chosen zero origin before scaling; shadow
texel offsets and depth corrections are differences; and clamped blocker
separation is explicitly promoted from a signed depth difference to an
absolute amount. The arithmetic test fixtures also had to stop borrowing
shadow-depthwhile silently assuming its old difference default. production-points-and-amounts-reject-invalid-arithmeticproves that negatingopacityreportscannot-negate-amountand adding twoworld-x-positionpoints reportscannot-add-points. Both shader errors retain the exact offending source form. The production quantity and shadow tests inspect the inherited characters and deliberate overrides.- A detached worktree at
2bd3dfesupplied the baseline. All seven block-world SPIR-V modules are byte-identical after the migration and passspirv-val; in particular the fragment hash remainsda53933e…and the sky vertex hash2bff3dd9….make smokestill producesedc597168a3ec79a04c6d2af53c5b1a5.
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.
Mentioned in: Generalize affine-p to a three-valued character, Review the exercised quantity architecture, Give the remaining crosshair colour ABI meaning, A staged concretization
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.
Mentioned in: Character: two orthogonal axes, and why the type cannot tell you
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:
| register | lanes |
|---|---|
vec3 world direction | xyz one homogeneous vector quantity |
vec3 packed surface input | xy texture sample point; z ambient occlusion |
vec4 texture result | rgb 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.
Mentioned in: Expressions, declarations, and bindings are objects, Where luv's semantic arithmetic goes beyond its sources, The frame uniform was an unlabeled quantity ledger
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:
- Arithmetic and field operators derive dimensions and tensor shape.
- Equal meanings permit intrinsically meaningful addition or comparison.
- A named domain operation explicitly interprets a compatible derived result.
- 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:
quantityconstructs a semantically typed literal or constant vector;assume-quantitymarks the auditable boundary where an opaque raw value is asserted to have a specification; andinterpretnames a compatible, already checked arithmetic result.
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.
Mentioned in: Dimensioned types in the shader DSL
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:
- one inspectable specification object;
- one representation object owning specialized arrays;
- a domain establishing the meaning of offsets;
- generic operations selected once for a whole phase; and
- an inner loop operating on unboxed values or SIMD packs.
The boundary performs the semantic check; the kernel does not redispatch on every lane. #R9M4PV follows this into the planned physics layout.
Mentioned in: Runtime objects, reader syntax, and the environment argument, What the package never had, Questions to keep open, Arithmetic definitions outlive execution backends, Lower checked domains to native SIMD kernels, One arithmetic medium, many clients, Field kernels bridge domains to semantic arithmetic
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.
Mentioned in: Declare quantities on Lisp storage
Referenced from code: 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. #OXBSAYdefclass represented-value-declaration declarations.lisp:48 ↗
defclass quantity-class records.lisp:52 ↗
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:
- One narrow metaclass with custom direct and effective slot definitions, exact inheritance rules, and live redefinition visible through ordinary MOP inspection.
- One thin structure definer which removes semantic options before expanding
to an ordinary
defstructand publishes the same declaration protocol. - Readable source declarations on
block-world-playerandsky-frame-parameters, withvec3and scalar representations unchanged. - Tests that inspect both definitions, reject conflicting inherited meaning, and show that construction and access still use raw values without quantity wrappers or per-access semantic dispatch.
Result:
quantity-classuses custom direct and effective slot definitions only for annotated slots. It retains:quantitybeside the ordinary MOP:type, propagates equal declarations through inheritance, permits independent Lisp type refinement, and reports contradictory inherited quantities when CLOS computes the effective slot. Redefining a class replaces the visible declaration in the ordinary live MOP.define-quantity-structremoves:quantitybefore emitting an ordinarydefstruct. One replaceable EQL method publishes the structure schema, so reevaluation does not leave obsolete per-slot methods behind. Both frontends implement the declaration protocol from #OXBSAY.block-world-playernow states world position, velocity, body dimensions, speeds, and accelerations beside its unchangedvec3anddouble-floatrepresentations.sky-frame-parameterssimilarly states its evaluated direction, colour, angle, exposure, and fog meanings beside the samedefstructlayout and accessors.- Five focused record tests cover MOP inspection, inheritance, live redefinition, semantic conflict, and raw structure access. Two luvcraft tests inspect the real records and construct ordinary raw values. A cold durable image loads the changed metaclass definition, and all 44 luvcraft tests pass.
Done when: satisfied. #GZ53LD now connects these declarations to checked CPU arithmetic at an explicit boundary.
Referenced from code: Define an ordinary structure whose annotated slots publish quantity meaning. The runtime representation and accessors are still those of DEFSTRUCT. #FLRFU8defmacro define-quantity-struct records.lisp:228 ↗
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:
- One small compatibility judgment which distinguishes representation failure from quantity failure and preserves useful source provenance from both sides.
- One real
block-world-playercalculation, likely integration of velocity over a duration into position, whose storage declarations supply the arithmetic contract rather than restating it in adapter code. - A deliberately mismatched slot or parameter rejected before the numerical function runs, while the successful path passes the original raw objects and performs no per-component semantic dispatch.
- Live and cold tests showing that the boundary composes with the Lisp compiler completed in #FTQEQD and leaves the shader realization independent.
Result:
ensure-declarations-compatiblecompares an actual storage declaration with a realization ABI. Quantity specifications and layouts must agree exactly; a concrete Common Lisp representation must be a known subtype of the expected representation. Its condition retains both source forms and distinguishes semantic from representation failure.lisp-arithmetic-realizationjoins one backend-neutral checked definition to explicit parameter and result representation types. Binding it to storage performs the compatibility checks once and returns the already compiled raw Lisp function. No quantity objects travel through that function.- The numerical Lisp protocol now admits representation extensions.
arithmetic/lisp/vec3.lispowns the transparent CPUvec3and implements its componentwise binary operations, dot, and normalization methods as a representation extension. The Lisp arithmetic protocol does not name the type, and neither it nor the representation depends on the block world. %predict-world-positionderives displacement from world velocity and frame duration, explicitly interprets the derived vector as a world-position difference, and adds it lawfully to a position point. Itsvec3realization is bound toblock-world-player's actual position and velocity slots plus a declareddouble-floatduration boundary. The resultingpredict-player-positionpasses the raw slot objects to compiled arithmetic; tests prove the value, input identity, and rejection of velocity substituted for position before numerical execution.- A cold image passes 21 core arithmetic, 6 Lisp realization, and 44 luvcraft tests. The present Lisp realization still chooses representation methods at each arithmetic operation, though never per component and never for semantic checking. Whether real kernels warrant representation-specialized lowering is deliberately left for the exercised-design review rather than assumed.
Done when: satisfied. #AVRNHW now broadens the declarations beyond the first player and sky records before that review.
Mentioned in: Declare quantities on Lisp storage
Referenced from code: Require Quantity specifications and layouts agree exactly. A NIL expected
representation leaves representation choice open; otherwise Predict unobstructed motion through the checked storage boundary. #GZ53LDdefun ensure-declarations-compatible declarations.lisp:99 ↗
actual storage to satisfy expected represented-value meaning.actual's Common
Lisp type must be a known subtype. Return actual on success. #GZ53LDdefun predict-player-position simulation.lisp:324 ↗
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:
- A source-led inventory of remaining unannotated quantity-bearing storage and APIs, grouped by simulation, camera, sky, lighting, meshing/render ABI, streaming, and measurement rather than one undifferentiated grep list.
- Declarations on the next coherent real owners, with exact quantity names and point/absolute/difference character where the code already establishes it.
- Checked CPU arithmetic at boundaries where it improves the code's semantic vocabulary; ordinary direct Lisp remains where a compiled definition would merely obscure a small local calculation.
- Focused tests and pushed checkpoints for each group, plus a running list of ambiguous values that require a domain decision instead of a guessed unit.
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:
- Camera position and angles, sky-clock state and keyframes, material lighting facts, and frame-performance durations now declare their semantic quantities. Timing stages retain distinct quantity names even though they all have the duration dimension: simulation time is not presentation time.
- Voxel, chunk, pixel, and draw extents remain deliberately unannotated where they are categorical indices or counts rather than continuous physical measures. The raw 0--15 sky/block propagation levels likewise remain distinct from the normalized light proportions sent to the GPU.
- The
voxel-spaceboundary is now explicit.:cellis a dimensionless but non-identity unit owned byLUVCRAFT.WORLD.QUANTITIES: it cannot silently compare equal to:oneor:metre. Continuous world/player/shader positions are measured in cells,cell-extentis a:voxel-cell-extentvector measured in metres per cell, and ray hits carry:ray-distancein cells. The application quantity system depends on that foundational vocabulary rather than defining world meaning above its owner. - Exercising this boundary corrected the first real design mistake in the migration: the initial player, camera, fog, and shader declarations called lattice values metres because the default cells happen to be one metre. Their representations and numerical behavior are unchanged; only the false equivalence was removed.
- Dense block, sky-light, and block-light columns now have ordinary voxel field
definitions (#SIYGXO). Raw u8 sky and block propagation levels carry
distinct quantity specifications while normalized GPU light proportions
retain their earlier meanings. Field definition identities travel with
live columns, solver-atlas copies, and immutable worker snapshots without
wrapping their lanes. The interleaved block-mesh vertex buffer remains a
heterogeneous repeated product rather than one scalar field. The lighting
work item likewise has one
levelrepresentation whose sky or block meaning comes from the owning queue. The queue now retains that exact field definition (#K2V9RD), rather than falsely annotating the raw slot in isolation. Ordinary function parameters remain an explicit frontend question rather than being forced into record declarations. - Session state now distinguishes the monotonic frame-clock point from the accumulated physics duration, and production results declare their elapsed duration. Lighting state now likewise declares its most recent reconciliation duration, while its counters remain counts. These calculations remain ordinary direct Lisp: record metadata should not be used as a pretext for wrapping a short subtraction when no checked arithmetic boundary needs it.
- Stable quantity-bearing constants now use
define-quantity-constant. The fixed physics step, collision clearance, shadow extents and depth biases, and shadow filter radii remain ordinary unwrapped numbers while publishing the same represented-value declarations as storage and arithmetic parameters. Structural counts such as shadow-map resolution, mesh stride, vertex count, and format version remain ordinary constants.
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.
Mentioned in: Check storage declarations against Lisp arithmetic One Mentioned in: Audit quantity-bearing Lisp storage The two removal frontiers are now small Mentioned in: Audit quantity-bearing Lisp storage The host declaration is independent of Mentioned in: Audit quantity-bearing Lisp storage The fullscreen sky triangle is a raw nine-float vector repeating one
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 Mentioned in: Audit quantity-bearing Lisp storage The source inventory distinguishes a function which owns a numerical law
from one which merely orchestrates already-owned values. The former belongs
in The concrete boundary map is: This pass removed the arbitrary eight-cell default from the reusable DDA:
The audit also classifies The durable SLY image accepted the new definition and constant declarations.
Strict Parinfer, shader generation plus Mentioned in: Audit quantity-bearing Lisp storage 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
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 The clip-origin failure also identifies the next missing concept without
pretending it is solved. 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
DONE Describe the repeated block-mesh vertex product #Q7M4PF
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
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.*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
: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.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
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.Domain Declared owners and boundaries Deliberately ordinary values camera and simulation camera/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 duration input state, collision control flow, pixel extents, lattice cell indices sky clock and keyframe slots; evaluated frame structure; frame uniform product interpolation control flow and cyclic mod after its inputs are ownedtargeting and world traversal camera position slot; camera-basis direction at the checked camera ABI; declared application reach; ray-hit distance slot DDA cells, steps, infinities, and occupancy control flow lighting field definitions; context-owned removal queues; material slots; reconciliation duration 0--15 field-domain codes, revisions, counters, and work tickets meshing and rendering repeated mesh, sky, and crosshair products; fixed camera/frame products; shader parameter and expression declarations; shadow constants vertex/count/stride indices, texture dimensions, and raw matrix rows owned by the semantic map streaming, production, and measurement production and lighting durations; frame-stage and benchmark structures chunk radii/counts, priorities, revisions, timeouts used only as operational API controls persistence and capture serialized 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 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.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.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
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.encoded-rgb arithmetic quantity.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.: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:
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.
Mentioned in: One arithmetic medium, many clients
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.
Mentioned in: One arithmetic medium, many clients, The stylesheet as Lisp definitions
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:
- The open quantity protocols themselves live in
:luv/arithmetic, while the vocabulary previously lived at the head ofluvcraft/shaders.lispin theLUV.SPIR-Vpackage. The names are domain meanings used at simulation and ABI boundaries, not properties of SPIR-V. luvcraft/quantities.lispnow publishes the unchanged kind and quantity definitions from theLUVCRAFT.QUANTITIESpackage. They are loaded by the:luvcraftowner after its renderer-independent:luvcraft/worldmodel.- A fresh SBCL image loaded the quantity files through
:luvcraftand resolved:world-positionas a length quantity. Full tests, generated-module validation, and the smoke render pass.
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:
+ - * / dot min maxalready derived quantity specifications inarithmetic/semantics.lisp, whileclamp,step,mix,smoothstep,normalize, andexptadded methods to that same generic fromhal/shader/language.lisp. Their contracts did not mention textures, stages, SPIR-V, or another shader-only concern.- The portable methods and non-CL operator symbols now belong to
LUV.ARITHMETIC.LUV.SPIR-Vimports and re-exports those exact symbol identities, retaining its established source vocabulary without owning the semantic rules. Live package reload removes the obsolete home symbols before importing their common identities. - Arithmetic tests exercise all six moved contracts without loading shaders;
shader tests assert the common/SPIR-V symbol identities. Strict Parinfer,
full tests, and
spirv-valpass. The five tracked production shader hashes remain byte-identical, and the smoke render completes at MD5226c4ffdd4515837d5c5a2d9fc415fa4. - A forced reload in the durable SLY image replaced the package definitions cleanly and reported EQ identity for all six common/SPIR-V operator names.
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:
- The new
:luv/arithmetic/languagesystem depends only on:luv/arithmetic.arithmetic/language/frontend.lispowns inspectable parameters, lexical bindings, literals, references, calls, quantity boundaries, unit conversions, one-expression function definitions, source reconstruction, and the EQL-specialized operator/parser protocols. - Common parsing checks quantity contracts while retaining source numeric
representation: its tests build a fog-shaped
let*graph, reject addition across different quantity spaces, expose an exact kilometre-to-metre conversion factor, and retain adouble-floatliteral rather than coercing it to a shader type. - Shader expressions now specialize these common nodes while adding shader types and lowering concerns. Existing shader accessors delegate to the common slots and traversal protocol, so current tools retain their public surface. Shader calls remain target-specific typed realizations; stages, interfaces, texture calls, maps, output statements, and SPIR-V provenance remain shader-owned.
- The source words
quantity,assume-quantity,interpret,representation, andconvert-unitnow have their symbol identity in the common language package and are imported and re-exported byLUV.SPIR-V. Shader tests prove both the identity and that production shader calls are also common arithmetic calls with the same reconstructed form. - A fresh image loads
:luv/arithmetic/languagewithout creating theLUV.SPIR-Vpackage. Focused common-language and shader suites pass after a forced live reload, andmake testpasses with all seven production shader modules byte-identical to the preceding commit. The smoke render remains MD5226c4ffdd4515837d5c5a2d9fc415fa4.
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.
Mentioned in: One arithmetic medium, many clients
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:
- The separate
:luv/arithmetic/lispsystem depends only on the common arithmetic language.arithmetic/lisp/compiler.lisplowers a checked definition to an ordinary lambda and then usesCOMPILE; the target function receives and returns ordinary numbers and vectors. define-lisp-arithmetic-functionis a thin definition form over the common source protocol. It publishes the same inspectable EQL-method definition asdefine-arithmetic-functionand emits a normalDEFUNfrom the checked lowering, so CPU callers use it like ordinary Lisp code.- Quantity construction, assumption, interpretation, and representation are compile-time-only boundaries in the emitted lambda. An explicit unit conversion becomes its exact numeric scale. Runtime numerical helpers operate over scalars or vector storage without quantity wrappers or per-element CLOS dispatch.
- Focused tests execute the fog-shaped
let*directly as a compiled double-float function, add and dot ordinary vectors, translate an affine point by a difference, lower kilometres to metres as an exact factor of 1000, and reject both point-plus-point and distance-plus-height before Lisp compilation. - A fresh Lisp image loads
:luv/arithmetic/lispand runs a standalone CPU definition without loading the SPIR-V package. Live SLY redefinition replaces both the semantic definition and ordinary function.make testpasses with all seven generated shader module hashes unchanged, andmake smokeretains MD5226c4ffdd4515837d5c5a2d9fc415fa4.
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.
Mentioned in: Check storage declarations against Lisp arithmetic, One arithmetic medium, many clients, Quantity-carrying shader types, Structural shader-type identity
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.
Mentioned in: Review the exercised quantity architecture, One arithmetic medium, many clients, Structural shader-type identity, A staged concretization
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.
Mentioned in: A columnar buffer is not one hot sequence, One arithmetic medium, many clients, A staged concretization
Referenced from code: Run the native-width squared-position oracle with a scalar tail. #VKLLPR Borrow BUFFER-TYPE's active extent, row declaration, and raw lane arrays.defun simd-test-position-energy columnar-simd-tests.lisp:100 ↗
defmacro with-columnar-buffer-storage columnar.lisp:515 ↗
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
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:
arithmetic/semantics.lispnow represents canonical symbolic dimensions and inspectable quantity specifications. EQL methods on ordinary arithmetic symbols derive addition, subtraction, products, quotients, and dot products.- Shader inputs, outputs, and uniform members can opt into
:quantity,:dimension,:unit, and:affine-pwithout changing theirshader-typeor SPIR-V representation. Arithmetic expressions retain the derived specification. - Once an annotation enters a shader expression, semantic derivation is total: an unannotated operand or an operator without a quantity rule is a parse-time error. Fully unannotated shader graphs remain valid while the vocabulary is grown deliberately.
minandmaxrequire exactly compatible specifications. Unit expressions compose through products and quotients, but there is no implicit common-unit selection::metreand:kilometreremain different until source performs an explicit conversion.quantityconstructs typed constants,assume-quantitymarks otherwise raw boundaries, andinterpretnames only a compatible checked result. All three alias their operand during lowering.interpretrejects raw values, unit changes, affine changes, and semantic relabelling.- Shader-specific EQL methods define the demonstrated semantic vocabulary:
sampleandsample-compareboundaries, vector constructors,clamp,step,mix, andsmoothstep. Once checking begins, an absent rule is an error rather than a reason to discard semantic information. arithmetic/tests.lisprejects equal-dimensional but differently named quantities, mismatched exact units, and illegal affine operations.hal/shader/tests.lispproves shadow-depth subtraction and compatiblemax, rejects point-plus-point, depth-plus-length, metre-plus-kilometre, missing annotations, and unsupported semantic operators, and produces byte-identical SPIR-V with and without semantic annotations.- A clean
mp-unitsreference checkout at commit20cedd5shows the useful separation between quantity specification and unit reference. Its arithmetic finds a common reference and converts operands; this experiment intentionally adopts the separation but requires exact units instead of implicit conversion. - The production block fragment graph now distinguishes shadow UV points and offsets, shadow-depth points and differences, cell-valued world spans, gradients, filter radii, light controls, and linear colour. Strict checking continues from the shadow result through lighting and fog to the final RGBA.
- The production fragment module remains byte-for-byte identical to the module
from before those annotations (SHA-256
da53933e77ce1a1f6123be4c5884aef43b0f28ddff849e12fe8125bc354bc494). Keepingreceiver-depthat its original demand point also demonstrated that source-order bindings are part of deterministic code generation even when semantic interpretation itself emits no instruction. - The shader workbench shows representation type beside semantic meaning for interface values, packed uniform members, bindings, and selected expressions; construction, assumption, interpretation, and packed component layouts stay visible and selectable in the expression tree.
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.
Mentioned in: Expressions, declarations, and bindings are objects, Quantity-carrying shader types
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:
arithmetic/semantics.lispdefines backend-neutralquantity-layoutandquantity-projectionobjects; inspectable quantity definitions own the ordered component names of homogeneous vectors.hal/shader/language.lispcarries layouts through interface variables, uniform members, references, and texture samples.swizzleaccepts only an exact declared layout projection or a lawful homogeneous projection.- The production block material declares
uv-shade.xyas texture UV andzas ambient occlusion; three independent light lanes; colour/diagnostic and shadow-control uniform packs; RGB/alpha atlas samples; and scalar depth samples. The sky ray crosses its vertex/fragment interface as one:world-directionvector whoseycomponent has its own quantity name. hal/shader/tests.lispproves heterogeneous packed projections, rejectsyzand whole-packed arithmetic, proves named homogeneous axes and texture sample channel layouts, distinguishesquantity,assume-quantity,interpret, andrepresentation, and checks their zero-codegen behavior.- The shader workbench presents packed lane maps and the four kinds of semantic boundary rather than flattening them into an undifferentiated annotation.
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.
Mentioned in: Expressions, declarations, and bindings are objects, The frame uniform was an unlabeled quantity ledger, Quantity-carrying shader types
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:
arithmetic/semantics.lispnow defines inspectableunit-definitionobjects behind an openunit-definition-forEQL protocol. The initial vocabulary included:one,:metre,:kilometre,:second, and:percentwith dimensions, canonical bases, and scales; #V3TSZA subsequently made their admissible quantity kinds explicit and added more coherent ISQ/SI units. Unknown unit symbols are errors.- A quantity specification derives its dimension from a declared unit and
rejects a contradictory explicit dimension. Compound expressions retain
exact named units;
unit-conversion-factorseparately proves a shared basis and computes source magnitude divided by target magnitude. convert-quantity-specification-unitpreserves name, physical dimension, tensor order, and affine character. It cannot perform semantic relabelling.hal/shader/language.lisprepresentsconvert-unitas a first-class typed expression with source provenance. A non-unity factor lowers to an explicit scalar or vector multiply; a unity factor remains a visible semantic node while safely aliasing its operand.- The shader workbench renders the conversion operand, target specification, and scale factor as a selectable expression rather than hiding the change in its generated arithmetic.
- Arithmetic tests prove kilometre/metre and percent/one conversion,
dimension inference, mismatch rejection, and undefined-unit failure.
Shader tests prove that
50 percentopacity becomes1/2 oneopacity through an emittedF-MUL, and reject raw, dimension-changing, and unknown- unit conversions. - The production shader binaries and their Vulkan validation remain unchanged; conversion is available without introducing implicit conversion into any existing operation.
Done when: achieved. Unit changes are numerical operations distinct from interpretation, and dimension-one units exercise the same mechanism as physical units.
Mentioned in: Calculation derives; interpretation names, Prove semantic arithmetic in the shadow graph
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.
arithmetic/semantics.lispnow defines inspectablequantity-kind-definitionandquantity-definitionobjects behind open EQL protocols. Kinds form a same-dimension parent hierarchy; quantity specifications expose their kind without replacing the exact quantity name used by arithmetic.- Every named unit declares its admissible root kind. The seed vocabulary now
includes
one, percent, per-mille, parts per million, metre, kilometre, second, hertz, radian, and steradian. Radian and steradian have the same physical dimension asonebut different semantic constraints. - Named construction and conversion require a declared quantity definition whenever a named unit imposes a kind. A missing definition and a dimensionally equal but inadmissible unit are hard errors; compound derived units continue to be checked dimensionally without inventing a result kind.
- The production shader vocabulary classifies normalized texture/shadow
coordinates, affine normalized depth, unit directions, proportions, relative
linear-colour signals, controls, gradients, sample counts, and world distance.
Their declarations now say
:unit :oneor:unit :metreexplicitly. - The lane called sun angular width is deliberately a normalized
:sun-disc-coordinaterather than an angle: its stored value is subtracted from a cosine alignment near one. Giving it radians would make the source sound more physical while describing the actual representation less truly. - Arithmetic tests accept an angle in radians and an opacity-like proportion in percent/one, reject proportion-in-radians, angle-in-steradians, and units on unregistered quantities. Shader tests exercise the same failures at the source-language boundary and inspect production quantity kinds.
- The shader workbench now includes the semantic kind in its quantity label, keeping name, kind, dimension, unit, tensor order, and affine character separately visible.
make testpasses strict Parinfer checks, all arithmetic/shader/project suites, generated-module validation, andmake smokerenders successfully. The production fragment remains byte-identical at SHA-256da53933e77ce1a1f6123be4c5884aef43b0f28ddff849e12fe8125bc354bc494.
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.
Mentioned in: Make numerical unit conversion explicit
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.
Mentioned in: Carry semantic units through the vertex and camera ABI
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:
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.
Mentioned in: Semantic maps are not merely matrix-shaped values, Three affine characters: point, absolute, difference, Constraints are marks the author places, not costs the compiler hides, Semantic representation boundaries point both ways, Where luv's semantic arithmetic goes beyond its sources
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:
- Semantic maps (#SM4DFB). mp-units gives tensor character and inner
products for the values a matrix holds; moppe's
atelier/matrix.hhconverts quantities to raw before asimd_float4x4; measures has no vectors at all. None can say what space a transform accepts, what it produces, or whether homogeneous division belongs to the operation.define-projective-shader-mapkeeps that relation as an inspectable object and lowers it byte-identically. This is the largest single advance over the source material. - Heterogeneous layouts as semantic products (#VC7VFY). mp-units'
vector_componentshandles the homogeneous case — a genuine vector quantity opting into typed axis decomposition — and luv adopts that. The extension is the recognition that a GPU register is often not a vector quantity but a packing (texture point plus ambient occlusion), where arbitrary swizzles must be errors and arithmetic on the whole is meaningless. mp-units' "why a quantity has a character" post argues the type cannot be trusted to reveal character; layouts are the constructive answer for representations we control.
Two source ideas were considered against the built system and declined, recorded here so they are not relitigated by accident:
- A field axis for character. mp-units splits character into field
(real/complex) and order after discovering phasor vectors and lossy
permittivity tensors were inexpressible. Luv carries order alone. No
rendering or block-world quantity is complex-valued, and mp-units' own
rule — "a character earns its place only when a real quantity cannot be
expressed without it" — is the reason to wait, exactly as they waited on
bivectors. If a Fourier-domain or phasor client ever appears, the
tensor-orderslot generalizes to a character pair. - Kind upcasting. mp-units lets
active_powerflow implicitly into apowerslot along the kind tree. Luv'ssame-quantity-space-prequires equal names for addition, so:view-distanceand:world-distancedo not add even though both are:length. That is stricter than the source, deliberately: the rendering vocabulary is small, its names are chosen to be exclusive, and an explicitinterpretis cheap.quantity-kind-subkind-pexists for unit admissibility, not for silent widening.
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:
luvcraft/shaders.lispnow declares camera and world positions as affine:world-positionvectors in cells, the three camera basis lanes as unit:world-directionvectors, projection scales inone, and fog near/far and projection depth offset as cell-valued:view-distancevalues.- Point subtraction derives a non-affine cell vector; its forward projection
is interpreted as view distance, while fog subtraction and division derive
a dimensionless progress value before interpretation as
:fog-amount. - The mesh ABI publishes texture UV plus ambient occlusion, unit normal, and three distinct light lanes on both sides of the vertex/fragment interface. In this iteration shadow UV and depth acquired their normalized meanings at the opaque shadow-transform result; #U0AHTQ subsequently replaced that assumption with a checked map. The shadow and sky vertex stages share the semantic frame declarations rather than reintroducing direction meaning downstream.
- #F83KGZ records the explicit
representationboundary used where a semantic metre point entered the then-raw homogeneous shadow rows. It was visible in the expression graph and shader workbench, required a semantic operand, and had no codegen effect; #U0AHTQ records its semantic replacement. - Shader tests inspect the affine/non-affine transition, exact metre units, interface layouts, representation boundary, and the rejection of a representative metre/one mix-up. The focused suite passes all 28 tests.
- Vulkan validation passes, and all five generated scene binaries remain
byte-identical: block vertex
9c99780b969b33eb242976c943463449b2cce4b66f779674a291fc3a4f2aeb34, sky vertex2bff3dd9c5b4ffe74746a324b05868b12f866749b7cc5670ae47d39a53925fac, shadow vertex4402a20bc62ef9289244ec0b52d2cd32f644147938d372eda308021ce49c300a, block fragmentda53933e77ce1a1f6123be4c5884aef43b0f28ddff849e12fe8125bc354bc494, and sky fragment2d7085ebfbe0c35e18cd96deb25369a28cbec391a55589ae747ff7ef8a0d9c48. make testpasses strict Parinfer, the complete project suites, and generated module validation;make smokerenders the block world successfully.
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.
Mentioned in: The frame uniform was an unlabeled quantity ledger, Quantity-carrying shader types
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:
hal/shader/language.lispnow provides inspectableshader-map-definitionobjects behind an open name protocol, a projective definition with coordinate scale/offset, aproject-pointexpression, and lowering specialized on the map definition class.- The first block-vertex version applied
:world-to-shadowdirectly to an affine:world-positionin cells. Its virtual packed result projected:shadow-uvand:shadow-depthwith their affine normalized meanings; the priorrepresentationand twoassume-quantitynodes left this path. The composition was subsequently split and renamed by #ZYCSYZ. - Undefined maps, raw or wrong-domain points, the wrong number of rows, and semantically annotated rows are source-language errors. Missing map semantics therefore cannot silently collapse to raw arithmetic.
- The virtual product lowers once and exact swizzles reuse its component IDs;
it adds no intermediate vector. Vulkan validation passes and the block
vertex binary remains byte-identical at SHA-256
9c99780b969b33eb242976c943463449b2cce4b66f779674a291fc3a4f2aeb34. - The shader workbench renders the map application as its own selectable expression, including the named map, semantic point, and represented rows. Shader tests inspect its domain, packed result, affine character, virtual materialization policy, raw rows, and failure contracts.
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.
Mentioned in: Carry semantic units through the vertex and camera ABI
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.
Mentioned in: Review the exercised quantity architecture, Share one production computation between CPU and GPU, Introduce the world-to-light sampling map
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:
| Meaning | Dimension or category | Shape / affine character |
|---|---|---|
| world position | L | vector point |
| displacement | L | vector difference |
| cell spacing or extent | L | scalar or vector difference |
| cell index, palette index | discrete index | not a physical length |
| duration | T | scalar difference |
| simulation instant | T | affine point |
| linear velocity | L T^-1 | vector |
| acceleration | L T^-2 | vector |
| mass | M | scalar |
| inverse mass | M^-1 | scalar |
| force | M L T^-2 | vector |
| impulse | M L T^-1 | vector |
| orientation | rotation structure | group element |
| angular velocity | T^-1 with angular meaning | axial vector |
| block content/state | categorical | palette-valued |
| light level | semantic dimensionless | scalar field |
| occupancy/coverage | semantic dimensionless | proportion field |
| probability, control, noise | semantic dimensionless | distinct 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.
Mentioned in: Addressing and metric are two sides of voxel space
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:
| quantity | unit |
|---|---|
| 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:
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:
- the pi factor: a Lambertian BRDF is albedo/pi per steradian, and dropping or double-counting the pi is a confusion between radiance and irradiance;
- linear versus sRGB: an encoded colour and a linear radiance have the same
representation and dimension but different units, and #N3R6WY applies
verbatim — moppe's
moppe_srgbdecode-at-point-of-use is itsin_metresfor colour; - depth confusions: NDC depth, reversed-Z device depth, and view-space forward distance are three scales for one length-like reading, and moppe's GTAO explicitly converts between two of them before any geometry is trusted;
- angular confusions: a dot product of unit vectors is a cosine, not an
angle; a sun's "angular width" lane may hold radians or a cosine
threshold, and nothing in a
floatsays which; - shadow bias: a receiver bias is a length in light-space depth units, whose correct magnitude is tied to the light projection's depth span — retuning it after a projection change is a unit conversion performed by hand and by eye.
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.
Mentioned in: Three abstractions: point, absolute, delta
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:
sun-vectorcarries a unit direction and, in w, a day factor — a proportion;sun-color-vectorcarries a linear radiance-like colour and, in w, an angular width;ambient-vectorcarries a colour and, in w, an exposure;shadow-control-vectorpacks shadow-map texel extents (texture-space lengths), a base bias, and a slope bias (light-space depth lengths) — four readings from three semantic families in one anonymous vec4;- the material mixes propagated light levels (normalized 0..15 readings squared through a response curve), an ambient-occlusion proportion, a surface emission intensity, and fog progress — all dimensionless, none interchangeable.
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:
- A shader type becomes a pair of representation and quantity
specification. Representation stays what it is today —
:float,:vec3— and remains the only thing lowering and SPIR-V ever see. The quantity specification rides the typed expression graph and is erased at lowering, so the check has zero GPU cost and no ABI consequence. - Operator methods derive specifications mechanically, exactly as they
already infer representation: multiplying a BRDF value by a radiance
yields a radiance-flavoured product;
dotof two unit directions yields a cosine kind;mixrequires its endpoints to share a specification; addition requires equal specifications, so radiance plus an sRGB-encoded colour fails at parse time with provenance pointing at the offending source form. - Interpretation stays at named boundaries, following #I9F3QB: a shader abstraction (#DXYHT4 machinery) such as a tonemap declares that it consumes scene radiance and yields display-linear colour; a decode declares sRGB in, linear out. Inference derives inside; abstraction signatures name and check at the joints.
- Inputs acquire meaning where they are declared: the uniform member list and vertex/resource declarations grow an optional quantity slot beside the representation. Because the same member list is spliced into every stage and sizes the host buffer, host-side Lisp and shader-side math would share one semantic declaration — the bridge that moppe could only open toward the GPU becomes checked on both sides, since both sides are Lisp.
- The shader lab gains an inspection dimension for free: every SSA value
already carries provenance to its source expression, so the McCLIM
detail pane can report "this lane is radiance" as easily as it reports
an inferred
vec3.
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.
Mentioned in: Types with structure; judgments in parallel, Quantity-carrying shader types
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.
Mentioned in: The prime-ratio encoding of dimension, What the package never had, What this page studies, Character: two orthogonal axes, and why the type cannot tell you, Logarithmic quantities as affine structure, Specifiers on the spec: one declaration site for meaning, Declarations keep representation and quantity parallel, One arithmetic medium, many clients, Quantity-carrying shader types, Structural shader-type identity
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 DSL owns its semantics; backends conform. Single-float
arithmetic, GLSL-flavoured
clamp/mix/smoothstepedge behavior, andfractconventions are defined once as language semantics. SPIR-V assembly and SBCL lowering (declared lambda forms, specialized arrays, eventually sb-simd packs) are two implementations required to agree; a divergence is a conformance bug against the language, not a property of one side. Precision and fast-math divergences will exist — one shared graph makes them enumerable and testable instead of ambient. - Domain iteration belongs to the driver; interior control flow is
language growth. The language never expresses the parallel loop over
the domain — the rasterizer drives the body per-fragment, a compute
dispatch per-invocation, and on the CPU an ordinary Lisp loop or field
combinator drives it per-lane. That is what keeps one body portable
across drivers. But iteration and branching within one element's
computation are ordinary demand-driven growth, per #SKSHDR's rule that
language growth is a normal feature, not an architectural exception.
The demand is already named: the sun-shafts raymarch (#BAP0QU) is a
data-dependent loop with early exit, and fBm clouds, tap kernels, and
blocker search are loop-shaped. A graded entry suggests itself: the
present compile-time unrolling by abstractions (the 17-tap shadow
disk); then bounded fold/sum constructs whose unroll-versus-loop
lowering is a backend choice with identical semantics; then
data-dependent loops with early exit, earned by the shafts client;
with
selectpreceding divergentif. The quantity judgment extends through control flow rather than around it: a fold's accumulator keeps one specification across iterations, and merge points require their incoming branches to agree — loop-invariance of meaning, checked where values meet. - Kernel signatures are where bundles meet the judgment. A bundle column carries a quantity specification (#B5L7VG); a kernel's inputs carry quantity requirements; the driver checks specification against signature once at the boundary, then runs raw lanes. The quantity system thereby spans storage and computation with one vocabulary and zero runtime tagging — the physics seam #R9M4PV gets dimension-checked impulse and force arithmetic over columnar state, without boxing a value. #327W2B makes the same boundary concrete for dynamically sized columnar buffers whose physical lane types are generated once while a materialization retains its exact row meaning.
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.
Mentioned in: Runtime objects, reader syntax, and the environment argument, What this page studies, Frontier programs are compiled sparse kernels
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.
Mentioned in: Logarithmic quantities as affine structure
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.
Mentioned in: Review the exercised quantity architecture, Quantity-carrying shader types
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:
register-shader-typestill interns flat keywords;:depth-texture-2dremains a distinct name rather than an image-type parameter. Replace the registry with interned structural instances keepingshader-type=eq-cheap.- New texture flavors (3D, array, cube, multisample) expressible as parameter values without new registry entries, as the Metal path (#DR3V60) will want.
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:
- equal-dimensional airspeed and climb-rate values cannot be added by the ordinary same-specification operation;
- water depth cannot stand in for coordinate spacing;
- a point cannot be added to another point;
- a scalar result cannot be interpreted as a vector result;
- composing fields from equal-size but different domains is rejected;
- a SIMD or foreign kernel sees raw contiguous lanes after one checked dispatch; and
- unit conversion is an explicit recorded operation rather than an implicit side effect of ordinary arithmetic.
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.
Convert a quantity to a compatible unit.
Construct a meaningful literal.
(name &key kind components non-negative-p
(character nil character-supplied-p))Define quantity NAME and any homogeneous COMPONENTS as members of KIND. NON-NEGATIVE-P declares a non-negative amount and defaults its character to :ABSOLUTE. CHARACTER may state :POINT, :ABSOLUTE, or :DIFFERENCE explicitly. Components inherit both.
(derived interpretation)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. …
Logical conjunction of tests and raw truth values.
Logical disjunction of tests and raw truth values.
(left right)(left right)Test whether two compatible scalars are equal.
Name a compatible derived quantity.
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
(declaration)Return DECLARATION's backend or Common Lisp representation type, or NIL.
The semantic meaning of a value, separate from its machine representation.
(declaration)Return DECLARATION's homogeneous quantity specification, or NIL.
Semantic quantities packed into disjoint positions of one representation.
(declaration)Return DECLARATION's heterogeneous quantity layout, or NIL.
(declaration)Return the source form which established DECLARATION.
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
(name-and-options &body slot-descriptions)Define an ordinary structure whose annotated slots publish quantity meaning. The runtime representation and accessors are still those of DEFSTRUCT. #FLRFU8
(name)Return the semantic structure schema named by NAME, or NIL.
The replaceable semantic schema emitted beside one ordinary DEFSTRUCT.
(options &key (default-tensor-order 0))Parse one source declaration plist into a quantity specification. Return NIL when OPTIONS contains no quantity attributes. This is the common source seam used by arithmetic parameters, shader interfaces, and semantic storage declarations; callers retain ownership of their source-specific error conditions and…
(actual expected)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
(left right)(left right)(player seconds)A canonical product of symbolic base dimensions raised to rational powers.
(buffer)((bindings buffer buffer-type) &body body)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…
One top-level defining form of a source file.
(name)Return the inspectable physical row layout named by NAME.
Headings, paragraphs, figures and their IDs, mentions, marks.
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
Multiplication and scalar scaling.
(name &key domain-type domain-quantity domain-dimension domain-unit
domain-affine-p sample-type sample-components
coordinate-scale coordinate-offset)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…
The V3 design replaces the two-way point/delta split with three abstractions, each mapped to a measurement scale and a mathematical structure: The observed operation table: absolute + absolute is absolute; absolute and delta mix freely (a signed delta shifts an absolute, with a contract check where the spec is…
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…
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…
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: –…
A cross-cutting pattern in all three designs: every semantic property lands as a declared specifier on the quantity_spec, never as a property of storage, units, or use sites. non_negative rides the spec and turns into contract checks at arithmetic sites. Character (field and order) is a spec property inherited…
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…
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: – arithmetic/semantics.lisp…
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…
Luv's arithmetic work already has the layers this design needs: – luv.arithmetic owns dimensions, units, named quantity meanings, tensor order, and point/absolute/difference character; – luv.arithmetic.language owns backend-neutral checked definitions and expression graphs; – luv.arithmetic.lisp lowers one such…
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…
Intent: execute arithmetic definitions on the CPU over ordinary Lisp numbers and vectors after checking their semantic contracts once. Evidence: – The separate :luv/arithmetic/lisp system depends only on the common arithmetic language. arithmetic/lisp/compiler.lisp lowers a checked definition to an ordinary lambda…
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…
Intent: describe the existing block-content, sky-light, and block-light columns without changing their representations or conflating field identity, per-site quantity, aggregate encoding, product state, and provenance. Evidence: luv.world.fields owns a small voxel-field-definition which is also a backend-neutral…
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…
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…
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…
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…
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…
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…
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…
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…
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…
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:…
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,…
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…
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…
These notes study the actively developed mp-units checkout at ~/src/mp-units, read as research literature for luv's own quantity system (#P2KN1D, #5N6SOQ). The most valuable material is not the core dimensional analysis — that part is well understood — but the places where the library's authors discovered that…
The measures package at ~/src/github.com/lispm/measures is the Cunis/Müller lineage of dimensioned numbers for Common Lisp: written by Roman Cunis (MAZ Hamburg), described in "A Package for Handling Units of Measure in Lisp" (Lisp Pointers 5(2), 1992), extended in the LOOM knowledge-representation system, and…
Moppe (~/src/moppe) is the predecessor engine: a C++/Metal terrain world with a complete cinematic renderer. It proves an ambitious end state on the actual development machines, preserves hundreds of tuned judgment calls, and shows what luv should improve: its answers are frozen in MSL and duplicated by convention,…
A GPU vector is a representation shape, not necessarily one mathematical vector. Similar vector registers can have different semantic structure: The second and third values are semantic products: operational packing puts several related values in one register without claiming that arbitrary lane combinations form…
Intent: when the HDR path (#WLHDRB, #VG8TGG) introduces exposure and adaptation, model exposure value as a base-2 log-domain point over luminance (photographic stops) and exposure compensation as a gain, as a small first client of the level/gain vocabulary from #BI6B1K, rather than tuning a bare multiplier lane.…
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: – hal/shader/language.lisp now provides inspectable…
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…
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…
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…
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…
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…
É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…
Intent: add a source-expansion layer above the core shader operator set, then define shadow vocabulary there instead of teaching the compiler a special shadow primitive. Evidence: – define-shader-abstraction adds an open EQL-method source vocabulary beside the core define-shader-operator protocol;…
Intent: give emissive materials headroom and bloom without abusing alpha or the current LDR scene target. Observed: the scene attachment is :rgba16-float, presentation applies exposure and a fitted ACES curve, and a quarter-resolution bright/blur/sweep chain adds bloom and shafts in linear light before that curve…
#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: – A shader type becomes a pair of representation and quantity specification. Representation stays what it is today — :float, :vec3 — and…
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…
Intent: make one inspectable definition and expression vocabulary usable by both CPU and shader realizations while retaining the shader workbench's source provenance. Evidence: – The new :luv/arithmetic/language system depends only on :luv/arithmetic. arithmetic/language/frontend.lisp owns inspectable…
The vertex and fragment shaders used by the playable block world live in luvcraft/shaders.lisp. Its complete material calculation is ordinary Lisp-shaped source: The vertex method is a CLOS graph too. Its built-in position output and shared frame uniform block are first-class declaration objects; member references…
Add the SPIR-V and expression-language capability before designing visual effects around its absence.
Moppe's shafts_gather_fragment marches view rays at half resolution through the existing shadow map, jitters them with interleaved-gradient noise, stops at scene depth, and weights lit spans by a forward Henyey-Greenstein lobe with g = 0.60. A separate pass filters and adds the result. Luvcraft already owns both…
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…
Maintain two roles with the same semantics but deliberately different loader and dispatch status. – A from-scratch reference relight clears a finite captured region, seeds known sky boundaries and emitters, imports explicit boundary values, and propagates to fixation. It favors obvious correctness and is loaded…
A small atelier might make the layers inspectable without designing a general symbolic algebra system: 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…
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.…
The backend becomes a complete rendering alternative when one unchanged shader-specification-for method can feed both target compilers and the Metal artifact preserves live publication semantics. One implementation sequence owns that threshold: #58IDSR has proved direct MSL source and compiler acceptance; #PH57K5 now…
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…
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: – Non-negativity is a compile-time fact by default. A non-negative specifier participates in the character algebra — it decides what an absolute may become, and it is what…
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…
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: – One narrow…
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…
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 three-way affine character: point, absolute, difference. These are the executable claims of the V3 operation table in wiki figure #LNRY72.