Sky, atmosphere, and voxel light for the little block world
Status: implementation notes and remaining plan #SK7L2T
This page records the sky and lighting system for luvcraft: an animated sky and primary light, caves and overhangs which become dark for the right reason, light-emitting blocks, relighting after edits and residency changes, and a clean route to HDR bloom and directional shadows.
The important outcome of the design review is that shader-language growth is part of the work, not a cost to evade. Frame-wide facts still belong in Lisp; spatially varying image mathematics belongs in shaders; and the boundary between them should become more capable as the renderer asks more of it.
Phases 0--4 and the first directional-shadow pass now have implementations in the codebase, and the HDR presentation stack the last phase asked for has landed: a linear floating-point scene attachment, a filmic curve, a bloom chain, and light shafts (#IC14P3). The sky it presents became procedural image mathematics rather than a two-colour gradient (#9SSXDJ), and the block surface grew a shape of its own from what the mesher knows about each face's edges (#J19EBO) and what the atlas paints beside each material's colour (#CHUKWD). A later round took the shading itself toward physical argument: no two faces are the same card any more (#3AVEKC), the specular lobe and the ambient are microfacet and hemispheric rather than fitted (#2T8CCH), the sky became an atmosphere (#8X33G2), and the cloud deck became a plane both halves of the renderer can find (#RSGLTL). Each phase below has a visible result and an evidence gate, so it can be revised from measurements without undoing the preceding phases.
What the critique changed #SKCRIT
The first draft had a useful inventory and a good instinct for dense chunk data, immutable mesh snapshots, and last-known-good shader replacement. It also optimized too hard for making no new machinery. These are the changes which make the plan durable.
- Grow the shader language on purpose. Avoiding
max,clamp,smoothstep, normalization, and exponentiation is not a design objective. They are ordinary graphics vocabulary needed immediately by fog, a sky gradient and sun disc, tunable light response, and later tone mapping. Add them as one coherent typed-math extension and keep their source-to-SPIR-V provenance inspectable. - Do not make the present world height a lighting invariant. The current
generated world resides only at chunk Y=0, but
block-worldandvoxel-spacealready admit arbitrary chunk coordinates and shapes. A 3x3-horizontal regional proof becomes false once a sky opening can affect a tall column. The runtime solver will therefore be incremental and height-independent; a from-scratch solver remains its oracle. - Keep source and derived revisions distinct. Block content revisions say that authored world data changed. Light revisions say that a derived field changed. Relighting must not impersonate a block edit by bumping content revisions; mesh dependency stamps should name the source and derived field revisions explicitly.
- Residency is a lighting input. Arrival and departure alter the boundary conditions of retained chunks. Eviction cannot merely invalidate a mesh and leave its baked light untouched. Lighting receives content and residency dirtiness, and unknown terrain is not silently reclassified as open sky.
- Add light in linear space. Screen blend is bounded and convenient, but it is not a useful foundation for several light sources and it destroys the headroom emissive surfaces need. The material sums sky, direct, and block illumination, adds self-emission separately, then fogs and eventually tone maps.
- Do not spend alpha as an undocumented bloom bus. Propagated blocklight is not the same fact as material emission, and the current sRGB scene target cannot retain HDR energy anyway. Emission gets an explicit material input; bloom follows a deliberate linear-HDR scene path.
- Measure scheduling claims. There is no evidence yet that a relight is sub-millisecond or that a one-mesh-per-frame ripple is pleasant. The system will expose work counters and latency, publish light atomically, and move captured work to the existing producer only if measurements justify it.
What exists today: verified touch points #SK9INV
hal/shader/language.lispis already an open shared frontend. Operators are symbols, and parsing, typing, result naming, and documentation are EQL-specialized generic functions. Thelower-shader-callprotocol is shared while SPIR-V and MSL own sibling methods and result products. A new operator should be a cohesive method cluster, not another central dispatch table.- The expression language has floats,
vec2..4, arithmetic,dot,mix,swizzle, texture sampling, and the first GLSL extended-math family:min,max,clamp,smoothstep,abs,sqrt,expt, andnormalize. A small shadow-ready follow-up addedstep, a typed:depth-texture-2dresource, and depth-image sampling that feeds ordinary scalar math throughswizzle. It now has expression conditionals and bounded counted folds, but no general loops, matrices, first-class boolean values, or general uniform packing. Uniform blocks intentionally consist only of aligned vec4 lanes. hal/vulkan/spir-v/instructions.lispnow modelsOpExtInstImportandOpExtInstas structured module sections in logical-layout order. Lowering requestsGLSL.std.450once per module when needed, while modules with no extended math acquire no import.- The live shader path in
luvcraft/live-pipeline.lisptransactionally rebuilds complete vertex/fragment pipelines and retains the last-known-good pipeline after a bad edit. New sky and post materials should use that path rather than a parallel compilation mechanism. - The block vertex shader and sky shaders share one frame uniform block, and the host derives the uniform byte size from the shader-visible layout. Fog now has explicit near/far semantics with clamped quadratic shaping.
- The visible material reads the shared sky/environment lanes, samples raw vertex light, computes sky, sun, and block contributions in linear space, adds surface emission, and then fogs.
- The mesh vertex is four
:float32x3attributes: position, UV plus AO, normal, and(sky block emission). The 48-byte stride fits the backend's current vertex format support. - Meshing copies a one-cell block and light halo to an immutable worker snapshot. Corner AO and corner light sampling use separate named rules, and mesh results are rejected when their dependency stamp is stale.
- The current scene target is the canvas's LDR sRGB format. It is rendered offscreen and then copied to the surface, which is a useful post-processing seam but not yet an HDR or multi-pass pipeline.
The rendering model #SKRMOD
There are two related systems, not one.
- The environment is continuous and changes every frame: sky radiance, primary-light direction and colour, ambient colour, fog, exposure, and time of day. Lisp evaluates its small time/profile model once per frame and writes a uniform block. Sky and material shaders evaluate spatial image functions from those parameters.
- The voxel light field changes when block content or residency changes: sky access, propagated blocklight, opacity, and emission. It is a dense derived field associated with resident chunks, revised independently from block content, and sampled into immutable mesh snapshots.
The block fragment material starts from a linear-light equation rather than a blend trick. In schematic form:
The exact response curves and balances are art parameters, not baked mesh facts. Vertices carry normalized raw levels; shader edits can change the curve without remeshing the world. AO modulates diffuse sky access, not self-emission. When the HDR phase arrives, radiance above 1.0 survives until tone mapping and can feed bloom naturally.
Propagated skylight is not a directional shadow map. It makes caves dark and canopies reduce ambient sky access. A separate GPU depth pass now shadows the moving sun; vertical canopy shade remains ambient access, not an angled sun shadow.
Phase 0: give the shader language the mathematics this work needs #SKSHDR
Add the SPIR-V and expression-language capability before designing visual effects around its absence.
Mentioned in: One arithmetic medium, many clients The first useful family is This phase does not add branching, loops, matrices, or every GLSL function
pre-emptively. It establishes the import/lowering machinery and a coherent
math baseline with immediate consumers. Later language growth remains a
normal feature, not an architectural exception. Unit tests cover successful and rejected signatures, a single shared import,
logical module ordering, deterministic lowering, and provenance. Generated
modules pass SPIR-V structure
OpExtInstImport and OpExtInst in the instruction vocabulary.spir-v-module explicit extension and extended-instruction-import
sections in SPIR-V logical-layout order. Imports are durable structured
objects, not instruction forms inserted ad hoc by individual shaders.GLSL.std.450 once when an operator needs it. Repeated
operators in one module share the import ID; modules which use no extended
math do not acquire one.Typed operator vocabulary
min, max, clamp, smoothstep, abs,
sqrt, expt, and normalize. Use CL's symbol where its meaning matches
the compiled subset; use exported shader symbols for words CL does not have.
Each operator gets explicit accepted scalar/vector signatures rather than a
promise that every GLSL overload exists. The existing EQL-method protocol is
the extension mechanism; a convenience definer may emit ordinary methods but
must not hide a registry.Uniform and vertex ABI checks
:float32x3 block vertex attribute for (sky block emission).
Do not quietly write :float32x2 into the plan while the backend rejects
it. General vertex formats remain a worthwhile GPU-API project when a
consumer benefits from them.Gate
spirv-val --target-env vulkan1.0. A live invalid edit preserves
the old pipeline, and a corrected use of an extended operator installs on the
running Vulkan path.
Phase 1: a real sky driven by a small Lisp clock #SKDYCY
A sky-clock belongs to the luvcraft session because it has mutable runtime
identity worth inspecting: time, rate, paused state, and an optional pinned
time for deterministic capture. A sky profile is ordinary data: keyframes
for sky/horizon colour, primary-light colour, ambient colour, fog, and
exposure. There is no class hierarchy or generic dispatch until more than one
actual sky model exists.
sky-frame-parameters evaluates the clock and profile once per frame. CPU
trigonometry is appropriate for a single sun direction; it is not a reason to
move per-pixel sky shaping out of the shader. The frame uniform grows by
aligned lanes for at least:
- sun direction plus day factor;
- sun colour plus angular size or intensity;
- zenith and horizon colours;
- ambient colour plus exposure;
- fog colour and near/far shaping.
The three hardcoded sky literals have been replaced by this one frame result. The clear value is merely a safe fallback.
A live :sky pipeline draws a fullscreen triangle before block geometry in
the existing scene pass, with depth writes disabled. Its fragment shader
reconstructs or receives a view ray and uses normalize, smoothstep,
max, and expt for a vertical gradient, horizon band, sun disc, and a soft
forward glow. A tiny static fullscreen vertex buffer is acceptable; integer
vertex-index support is not a prerequisite unless it is independently useful.
The block shader reads the same environment lanes directly. Fog becomes a clamped function with explicit near/far semantics. The sky pass itself is not fogged into its own colour.
The clock advances in render-luvcraft-frame from its bounded delta. SLY can
pause it, set a time, change its rate, or replace the profile without
restarting. Smoke capture pins it; interactive play animates it.
Mentioned in: Sky, temporal reconstruction, and mesh shaders bound the proof Noon, dusk, and night captures show a continuous sky, matching fog, and a
moving primary light in the actual renderer. The shader lab can inspect the
sky specifications. A fixed clock produces a byte-identical smoke image;
unpinning it visibly advances without changing block content or remeshing.Gate
Phase 2: make voxel light an explicit derived field #SKSTOR
Each resident block-chunk may own a chunk-light-field with:
- sky and block arrays of
(unsigned-byte 8)values constrained to 0..15; - a light revision and six light-boundary revisions;
- a state such as
:unlit,:stable, or:provisional.
The semantic light-field object belongs at the chunk boundary; its 4096-site columns remain dense arrays. Two u8 arrays cost 8 KiB for a 16-cubed chunk. Nibble packing is deferred until memory or bandwidth measurements say it matters.
Block light behavior is distinct from collision. Add both protocols now:
0..15
0..15
linear material intensity
Air answers zero for all three. A block-kind method reads configured slots
with opaque/non-emissive defaults; particular kind instances carry the data.
Propagation strength and visible surface radiance are separate even when one
crystal configures both. The solver dispatches once per palette entry to
build dense opacity and light-emission lookup vectors, then its hot loops index
those vectors. It does not call a generic for every cell.
Light publication advances light revisions only. The mesh dependency stamp contains the target chunk's content and light revisions plus the opposing content and light boundary revisions of sampled neighbors. That preserves the reason a mesh became stale and avoids feedback in which derived light looks like another authored edit.
This field is a sibling materialization over the chunk's domain, not a second
spatial domain merely because it has its own revisions. The current
light-region nevertheless rebuilds world decomposition, dense offsets, and
face enumeration, while meshing rebuilds related traversal for its resolved
neighborhood and immutable halo. #WUB581 records that concrete duplication;
#K3KZTG makes the shared domain/window vocabulary the next structural proof.
Lighting semantics and boundaries #SKBOUND
- Blocklight begins at
block-light-emissionand loses at least one level per six-neighbor step, with opacity contributing further loss. - Direct sky enters from a boundary known to be open to the sky. It travels downward through transparent cells without losing a level; lateral and upward propagation attenuate. This rule works across any number of vertical chunks.
- A missing resident neighbor has one of three meanings: known open boundary,
known closed/outside boundary, or unknown terrain. Only the world/source
policy may decide which. The lighting solver never equates
:absentwith open sky merely because the mesher currently chooses:airfor missing geometry. - Provisional results may be rendered at a residency edge, but their dependency includes that boundary state. Arrival or departure dirties the retained face and schedules reconciliation before a new stable light revision is published.
- Residency events accumulate until reconciliation. A same-key remove-and-arrive replacement must run both sides of the transition: remove the old incarnation's influence from retained neighbors, then seed the new resident chunk.
The little generated world can initially declare open sky above its known
vertical extent and a closed lower boundary. A future vertically streamed
source can provide sky-column knowledge or retain :unknown until the upper
stack is known. That source protocol, rather than a hardcoded chunk Y=0 test,
is the growth seam.
The solver: explicit reference truth plus one runtime implementation #SKRELT
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 only
by the explicit
luvcraft/light-referencetest/reference system. - The runtime relighter maintains removal and addition queues. Content changes first recompute direct-sky seeds for affected columns. Decreased or invalidated values enter the removal queue; surviving sources and alternate paths enter the addition queue. The same mechanism crosses resident chunk faces and works for both edits and residency transitions.
The old 3x3-horizontal recomputation remains useful historical test evidence,
not a current-world fallback. Production dispatch accepts only :compiled;
unknown, misspelled, and retired solver names signal immediately. The
reference system can still compare a complete captured world without placing
the oracle on the operational recovery path.
A luvcraft-lighting-state owned by the session records dirty cell/column or
chunk coordinates, residency-boundary changes, queue counters, and an
in-progress candidate. World content hooks and residency publication feed
that state; a dirty bit stored only on a chunk would lose departures and give
too little information for future vertical columns.
Reconciliation runs before mesh snapshots are captured, but it does not expose half-solved arrays. Work is accumulated in candidate copies of touched light fields and published as one owner-thread transaction when the queues settle. Until then meshes continue to use the previous stable revision. A new edit either merges into or invalidates the candidate explicitly.
Start with bounded owner-thread work and record cells popped, chunks touched,
milliseconds, restarts, and edit-to-publish latency. If real captures show
hitches, capture immutable block/light/boundary inputs in a new production
request and publish a complete candidate through the existing
perform-production-request / publish-production-result protocol. Its
dependency stamp rejects stale results. Do not add a second worker system.
Mentioned in: One arithmetic medium, many clients, Discover, relax, and invalidate are three different dynamics
Meshing carries raw light, not an art-directed bake #SKMESH
make-block-mesh-snapshot copies sky and block u8 columns for the same
one-cell halo as block samples, after stable lighting publication. A
sample-light-at generic is justified at this representation boundary: the
live world/neighborhood and immutable snapshot each provide one method, while
the dense inner representation remains arrays.
Corner lighting samples the face-adjacent air cells around each vertex. It
may share the existing AO traversal after a test establishes the intended
sample coordinates, but AO occupancy and light averaging remain separately
named results. Occluded samples and absent/unknown samples have explicit
rules rather than falling through block-solid-p.
The mesh adds one attribute at location 3:
light-material-input :vec3 = (sky-level / 15,
block-level / 15,
surface emission)That widens the current 36-byte vertex to four :float32x3 lanes and a
48-byte stride, which the backend can describe today. It is intentionally
plain for the first proof. Record generated vertex bytes and mesh time; if
bandwidth becomes material, add normalized packed vertex formats to the GPU
API as a measured optimization rather than obscuring the initial format.
Light revision changes schedule remeshing through the existing snapshot and stale-result machinery. Relit visible chunks receive priority; an artificially slow “pleasant ripple” is not an acceptance criterion. Measure solver completion and mesh publication separately so either bottleneck is visible.
Mentioned in: Meshing also carries what a fragment cannot see Caves, overhangs, and canopy shade work across chunk seams. Arrival and
eviction do not leave stale bright or dark borders. A vertical multi-chunk
test world works even though the current generator resides at Y=0. Random
edit and residency sequences leave the runtime field bit-identical to the
reference relight after queues settle.Gate
Meshing also carries what a fragment cannot see #J19EBO
#SKMESH establishes why light readings belong in the vertex product: they are what the mesher knows and the fragment stage does not. Face edges are the same kind of fact, and they turn out to be the difference between a block world which reads as carved solids and one which reads as a lit grid.
A block face is flat geometry, but a real surface rounds over where it ends and fillets where it meets a wall. Perturbing the shading normal near the face's boundary gives both, and needs no mesh change beyond knowing where the fragment sits inside its cell. Doing it at every boundary, though, draws a seam across the middle of an open plain, where the face merely continues into an identically oriented neighbour and there is no edge at all. A fragment cannot tell those cases apart; the mesher can.
So block-face-edge-shaping-components classifies each face's four in-plane
edges once, from the same neighbourhood the corner occlusion already samples:
| classification | what lies across the edge | what the surface does |
|---|---|---|
| concave (-1) | a block rises at the edge | fillet inward, gather occlusion |
| flush (0) | the same plane continues | nothing |
| convex (+1) | nothing | round over |
The four signed values become a fifth vertex lane, :float32x4, and the
fragment stage multiplies each of its four boundary ramps by the matching
sign. One expression therefore serves all three cases, and the sign is what
turns a round-over into a fillet. The two in-plane axes are chosen from the
face normal by the same rule on both sides of the ABI, which is what makes U
and V mean the same thing to the mesher and to the shader.
This widened the vertex from a 48-byte to a 64-byte stride, and required the
portable vertex-attribute vocabulary to grow past :float32x3 alone --- a
gap #SKMESH's "four :float32x3 lanes" had left standing in both backends.
Two later corrections, both about making the round-over read as a shape (#YUB8CH).
Mentioned in: Status: implementation notes and remaining plan, A round-over is an arc, not a leaning plane, Decisions now made; questions left to evidence
A round-over is an arc, not a leaning plane #YUB8CH
The first version of #J19EBO's shaping added a scaled tangent to the face normal and normalized the sum. That can only ever reach the arctangent of the scale --- a strength of 0.42 buys 23 degrees --- so a legible edge needs an implausible scale, and even then the shading bunches into the last sliver of the ramp because the tangent grows linearly while the angle saturates.
Rotating the normal through an angle instead sweeps it evenly from the face toward the edge, which is what a fillet of a given radius actually does. The in-plane lean gives the direction and the ramp gives the fraction of a quarter turn, and the normal is the corresponding point on that arc. The guarded denominator matters more than it looks: over the flat middle of a face the lean is exactly zero, so the direction is undefined and only the guard keeps it from being a division by zero --- but the angle is zero there too, so the undefined direction is multiplied away.
The second correction is that a round-over is only legible if it is a few pixels wide. Held to a fixed fraction of a cell it dwindles as blocks recede, and everything in the middle distance flattens back into a card long before it is genuinely unresolvable. The block material already measures its own texel footprint from screen derivatives of the atlas coordinate, for the relief fade; the same measurement grows the bevel width until the edge is worth drawing, bounded above so it never eats the face it is an edge of, and then fades it out. So the two shapings share one measurement and use it in opposite directions: relief shrinks with distance, the round-over widens.
Mentioned in: Meshing also carries what a fragment cannot see, No two faces are the same card
The atlas paints relief as well as colour #CHUKWD
Colour is only half of what a material looks like. The other half is its
micro-surface: whether it is granular, grooved, tufted, or faceted. luvcraft's
atlas was already generated arithmetic rather than an asset, one
paint-block-atlas-tile method per numbered tile, so the honest place for
that half is the same arithmetic. paint-block-atlas-relief paints a height
field; colour variation reads that field, and make-block-normal-atlas
materializes its tile-clamped central difference as a linear tangent-space
normal map. The block material reads that map once instead of reconstructing
the gradient with four dependent texture taps per fragment.
The colour atlas stays sRGB with ordinary opaque coverage. The normal atlas
is linear RGBA8: RGB is the encoded unit normal and alpha retains the source
height for inspection. The shader declares those sampled meanings as
:surface-normal-sample and :surface-relief rather than treating channels
as unlabeled numbers. #SKOPEN's decision that bloom must not commandeer
scene alpha remains independent of both material textures.
Relief and bevel are both sub-block detail, so both fade out on a measured texel footprint from the atlas UV derivatives: distant terrain neither sparkles nor shows a lit grid. A material's relief is one EQL method, so a live image can re-sculpt grass tufts or stone cracks and rebuild both dense atlases without touching the rest of the palette.
Mentioned in: Status: implementation notes and remaining plan, No two faces are the same card, A roughness the relief already knew, HDR crystal bloom, Decisions now made; questions left to evidence
Referenced from code: Return the 0..255 surface height of atlas tile Like the colour, each named tile is one EQL method, so a live image can
re-sculpt a single material's micro-surface and rebuild both atlases without
touching the rest of the palette. See #CHUKWD for why relief and normals are generated beside colour rather
than arriving as authored assets.defgeneric paint-block-atlas-relief blocks.lisp:362 ↗
tile at tile-local X,Y.
No two faces are the same card #3AVEKC
The atlas has one tile per material and the world has thousands of faces per tile, so #CHUKWD's relief only made every grass block the same card seen again in better light. Three fields now make each face its own, and none of them costs a texel.
A hash of the cell a face belongs to is constant across that face and different on the next one. The cell is found by stepping half a cell back along the face's own normal, which lands inside the block whichever of the six faces this is. Value noise over the world point knows nothing about where faces begin, so it drifts across a whole plain the way ground does, at a scale of about a dozen cells. A grain finer than a cell supplies the last octave, and --- the part that matters --- two more taps of that grain, a step along each of the face's own in-plane axes, give its gradient there, which leans the shading normal. The ground is then not merely painted unevenly but lit unevenly, which is what the eye actually reads as texture.
Value and hue move together but not identically: brighter patches are also
warmer, and a face's own constant drifts its hue as well as its value, so a
plain of grass is many greens rather than one green at many brightnesses.
Every scale fades out on the footprint that scale resolves at --- the
texel-scale terms on the relief fade, the cell-scale ones on the bevel fade of
#YUB8CH --- and all three amplitudes ride one surface-detail knob.
Mentioned in: Status: implementation notes and remaining plan, Materials and sky taken toward physical shading
Referenced from code: --- the shaped surface -------------------------------------------
A block face is flat geometry, but it should not read as a flat
material. Two shapings ride on the same face: a bevel that rounds
the last sixth of a cell toward its edge, and a per-texel relief
read straight out of the atlas the material is already painted in.
Both are pure normal perturbation, so no mesh changes. A block face's own normal selects its two in-plane world axes, so
one expression serves all six faces without a branch. The atlas coordinate and its screen derivative size both shapings.
The normal itself is already the central difference of the same
procedural relief field that paints the colour atlas. Texels per pixel: one cell is TILE-TEXELS of them. Both shapings are sub-block detail, so both have to fade out as
soon as they stop resolving on screen; otherwise distant terrain
sparkles and shows a lit grid instead of a surface. One measured
footprint drives both, at their own scales. The mesher's edge classification decides what each boundary does.
A convex edge rounds the surface over; a concave one fillets it
into the inner corner; a flush one -- the middle of an open plain,
where the face simply continues into an identically oriented
neighbour -- leaves the surface alone. Signs come straight from
the vertex lane, so the same ramp expression serves all three. A round-over is only legible if it is a few pixels wide. Held to
a fixed fraction of a cell it dwindles to nothing a short way off,
and every block in the middle distance flattens back into a card;
grown with the footprint it keeps its shape until it is genuinely
too small to resolve. The upper bound stops it from eating the
face it is supposed to be an edge of. An inner corner is a crevice, and a crevice gathers occlusion.
Only the filleted edges contribute; a rounded-over outer edge is
more exposed than the face it belongs to, not less. One linear texture read replaces the four height taps formerly
used here. The CPU generator clamps those taps within each tile,
normalizes the result, and stores the source height in alpha for
inspection. Fade the decoded tangent normal back toward the flat
face as its texels become sub-pixel. A real round-over is an arc, not a leaning plane. Adding a scaled
tangent to the face normal can only ever reach the arctangent of
that scale, so a strong edge needs an implausible scale and still
bunches its shading into the last sliver. Rotating the normal
through an angle instead sweeps it evenly from the face toward the
edge, which is what a fillet of that radius actually does. The
guarded denominator matters: over the flat middle of a face the
lean is exactly zero, and the angle is zero with it. --- the weathering -----------------------------------------------
The atlas has one tile per material and the world has thousands of
faces per tile, so on its own every grass block is the same card
seen again. Three fields fix that and none of them costs a texel:
a hash of the cell a face belongs to, constant across that face and
different on the next one; value noise over the world point itself,
which knows nothing about where faces begin and so drifts across a
whole plain the way real ground does; and a grain finer than a cell
whose own gradient leans the surface, so the ground is not merely
painted unevenly but lit unevenly. The cell is found by stepping
half a cell back along the face's own normal, which lands inside
the block whichever of the six faces this is. #3AVEKC Two more taps of the same grain, a step along each of the face's
own in-plane axes: their differences are the field's gradient
there, which is exactly the tilt a bump of it would give. Brighter patches are also warmer and duller ones cooler, and a
face's own constant drifts its hue as well as its value: a plain
of grass wants to be many greens, not one green at many
brightnesses. The grain is a cell-wide feature, so it survives to the distance
a cell does rather than the distance a texel does. --- the micro-surface --------------------------------------------
The atlas normal is this material's own slope at texel scale, and
the best evidence available about what happens below a texel: a
steep texel is a rough material. When the relief fades out with
distance its variance has to go somewhere, and the honest place is
the roughness -- Toksvig's argument -- so a highlight keeps its
size across the draw distance instead of sharpening into a
sparkle on every distant face at once. #2T8CCH A PCF tap displaced across a receiver plane must compare against
that plane's depth at the tap, not the center fragment depth.
With the orthographic shadow rows, the analytical UV depth gradient
is the face normal expressed in the light basis and scaled by the
world-span/depth-span ratio. Its denominator is bounded only near
grazing incidence, where direct sun is negligible. This prevents
wide kernels from reading the receiver's own slope as an occluder. A surface turned away from the light has no shadow decision to
make: its own geometry already excludes the sun, the receiver
plane's depth runs almost parallel to the light so every filter
tap lands a large and increasingly ill-conditioned distance away,
and the map's answer degenerates into noise. Near noon that is
every vertical face in the world at once, which is why the
shimmer was worst there. Fading the decision out toward "lit"
exactly where it stops meaning anything costs nothing visually --
the same cosine multiplies the direct term to zero -- and gives
the specular lobe and the diagnostic a stable value instead of
the noise. The lower edge is where the receiver-plane denominator above
starts being clamped: SHADOW-FORWARD is the negated sun, so that
dot product is exactly -N-DOT-L, and the clamp engages precisely
below 0.05. The two mechanisms therefore hand over rather than
overlap, and the clamp never has to carry a visible decision. The mesh carries normalized raw light readings; every response
curve and balance below is an art parameter editable live
without remeshing the world. Lateral skylight gives ambient visibility but not a hard sun
beam; the shadow map gates only the direct solar term. --- the deck's shadow --------------------------------------------
The sky draws a cumulus deck on a plane at a fixed world height,
and the ground under it should know. Following the sun up from
this point to that plane and asking the very same field the very
same coverage question is the whole of it: the deck's own shadow
sweeping across the world, which is what keeps a plain at noon
from being one flat sheet of light. The threshold is wider than
the sky's, because a shadow thrown from that far off is blurred
by the sun's own angular width long before it lands. #RSGLTL Occlusion bites harder than the raw mesh reading: the corner where
three blocks meet is what tells the eye these are solid volumes. --- what is not the sun ------------------------------------------
Everything above a face that is not the sun is still the sky, and
the sky is not one colour: a face turned up sees the zenith, a
face turned sideways sees the horizon, and a face turned down sees
what the ground bounced. One interpolation over the normal's own
vertical component is the whole of it, and it is the difference
between blocks standing in a world and blocks floating in a grey
room. The profile's ambient colour stays in the mixture: it is
the art direction's say over light the geometry cannot explain. A small floor keeps unlit geometry barely readable rather than
a void; caves stay dark for the right reason. The ambient term
is deliberately weaker, and the sun correspondingly stronger,
than a display-referred renderer could afford: the filmic curve
on presentation is what brings the sunlit half back down. --- the microfacet lobe ------------------------------------------
One GGX lobe off the shaped normal, with Smith's height-correlated
visibility and a dielectric's Fresnel: blocks are not metal, so
four per cent at normal incidence and the whole of it at a grazing
one. The roughness above decides everything about its shape, so a
polished face and a tufted one differ here without differing
anywhere else. Smith's height-correlated visibility already carries the
1/(4 cos cos) the microfacet specular would otherwise divide by. The cosine belongs in the specular lobe as much as in the diffuse
one: without it a highlight can appear on a face the sun cannot
reach, carrying whatever the shadow map happened to say there. The same sky again, in the direction the surface actually
reflects: the sheen that tells a smooth face from a rough one
out of the sun, and the one term that makes a block look like it
is standing under this weather rather than under a light bulb. Distant terrain fades into exactly the colour the sky's own ground
half arrives at, warmed where the ray runs toward a low sun: the
aerial perspective that puts a mile of air between here and the
horizon.define-shader-method shader-specification-for shaders.lisp:1218 ↗
A roughness the relief already knew #2T8CCH
The block material's specular was one fitted power under a Fresnel weight. It is an ordinary microfacet lobe now --- GGX distribution, Smith's height-correlated visibility in the form that already carries the 1/(4\cos\theta_l\cos\theta_v), Schlick's Fresnel at a dielectric's four per cent --- which would be unremarkable except for where its roughness comes from.
Nothing in the palette declares a roughness. But #CHUKWD's normal atlas is each material's own slope at texel scale, and a steep texel is the best evidence available about what happens below a texel: grass tufts are rough, a glass pane is smooth, and the atlas already knows which is which. So the roughness is read from the tangent normal's tilt. When that relief fades out with distance its variance has to go somewhere, and the honest place is the same roughness --- Toksvig's argument --- so a highlight keeps its size across the draw distance instead of sharpening into a sparkle on every distant face at once.
What is not the sun also stopped being one grey. A face turned up sees the zenith, a face turned sideways the horizon, and a face turned down what the ground bounced; one interpolation over the normal's own vertical component, against the zenith and horizon colours the frame environment already carries for the sky, is the whole of it. The same sky evaluated in the direction the surface actually reflects, weighted by roughness and a grazing Fresnel, gives the sheen that tells a smooth face from a rough one out of the sun. Those two terms are the difference between blocks standing under this weather and blocks standing under a light bulb.
One thing had to move that is not shading at all. The palette was authored
display-referred, before the renderer worked in linear radiance, and sand at
0.59 linear reflects more light than fresh snow does. A microfacet lobe over
an albedo that bright is a white card with a highlight on it, so the natural
materials came down to the range a real one occupies and the exposure came
down with them.
Mentioned in: Status: implementation notes and remaining plan, Materials and sky taken toward physical shading
Referenced from code: --- the shaped surface -------------------------------------------
A block face is flat geometry, but it should not read as a flat
material. Two shapings ride on the same face: a bevel that rounds
the last sixth of a cell toward its edge, and a per-texel relief
read straight out of the atlas the material is already painted in.
Both are pure normal perturbation, so no mesh changes. A block face's own normal selects its two in-plane world axes, so
one expression serves all six faces without a branch. The atlas coordinate and its screen derivative size both shapings.
The normal itself is already the central difference of the same
procedural relief field that paints the colour atlas. Texels per pixel: one cell is TILE-TEXELS of them. Both shapings are sub-block detail, so both have to fade out as
soon as they stop resolving on screen; otherwise distant terrain
sparkles and shows a lit grid instead of a surface. One measured
footprint drives both, at their own scales. The mesher's edge classification decides what each boundary does.
A convex edge rounds the surface over; a concave one fillets it
into the inner corner; a flush one -- the middle of an open plain,
where the face simply continues into an identically oriented
neighbour -- leaves the surface alone. Signs come straight from
the vertex lane, so the same ramp expression serves all three. A round-over is only legible if it is a few pixels wide. Held to
a fixed fraction of a cell it dwindles to nothing a short way off,
and every block in the middle distance flattens back into a card;
grown with the footprint it keeps its shape until it is genuinely
too small to resolve. The upper bound stops it from eating the
face it is supposed to be an edge of. An inner corner is a crevice, and a crevice gathers occlusion.
Only the filleted edges contribute; a rounded-over outer edge is
more exposed than the face it belongs to, not less. One linear texture read replaces the four height taps formerly
used here. The CPU generator clamps those taps within each tile,
normalizes the result, and stores the source height in alpha for
inspection. Fade the decoded tangent normal back toward the flat
face as its texels become sub-pixel. A real round-over is an arc, not a leaning plane. Adding a scaled
tangent to the face normal can only ever reach the arctangent of
that scale, so a strong edge needs an implausible scale and still
bunches its shading into the last sliver. Rotating the normal
through an angle instead sweeps it evenly from the face toward the
edge, which is what a fillet of that radius actually does. The
guarded denominator matters: over the flat middle of a face the
lean is exactly zero, and the angle is zero with it. --- the weathering -----------------------------------------------
The atlas has one tile per material and the world has thousands of
faces per tile, so on its own every grass block is the same card
seen again. Three fields fix that and none of them costs a texel:
a hash of the cell a face belongs to, constant across that face and
different on the next one; value noise over the world point itself,
which knows nothing about where faces begin and so drifts across a
whole plain the way real ground does; and a grain finer than a cell
whose own gradient leans the surface, so the ground is not merely
painted unevenly but lit unevenly. The cell is found by stepping
half a cell back along the face's own normal, which lands inside
the block whichever of the six faces this is. #3AVEKC Two more taps of the same grain, a step along each of the face's
own in-plane axes: their differences are the field's gradient
there, which is exactly the tilt a bump of it would give. Brighter patches are also warmer and duller ones cooler, and a
face's own constant drifts its hue as well as its value: a plain
of grass wants to be many greens, not one green at many
brightnesses. The grain is a cell-wide feature, so it survives to the distance
a cell does rather than the distance a texel does. --- the micro-surface --------------------------------------------
The atlas normal is this material's own slope at texel scale, and
the best evidence available about what happens below a texel: a
steep texel is a rough material. When the relief fades out with
distance its variance has to go somewhere, and the honest place is
the roughness -- Toksvig's argument -- so a highlight keeps its
size across the draw distance instead of sharpening into a
sparkle on every distant face at once. #2T8CCH A PCF tap displaced across a receiver plane must compare against
that plane's depth at the tap, not the center fragment depth.
With the orthographic shadow rows, the analytical UV depth gradient
is the face normal expressed in the light basis and scaled by the
world-span/depth-span ratio. Its denominator is bounded only near
grazing incidence, where direct sun is negligible. This prevents
wide kernels from reading the receiver's own slope as an occluder. A surface turned away from the light has no shadow decision to
make: its own geometry already excludes the sun, the receiver
plane's depth runs almost parallel to the light so every filter
tap lands a large and increasingly ill-conditioned distance away,
and the map's answer degenerates into noise. Near noon that is
every vertical face in the world at once, which is why the
shimmer was worst there. Fading the decision out toward "lit"
exactly where it stops meaning anything costs nothing visually --
the same cosine multiplies the direct term to zero -- and gives
the specular lobe and the diagnostic a stable value instead of
the noise. The lower edge is where the receiver-plane denominator above
starts being clamped: SHADOW-FORWARD is the negated sun, so that
dot product is exactly -N-DOT-L, and the clamp engages precisely
below 0.05. The two mechanisms therefore hand over rather than
overlap, and the clamp never has to carry a visible decision. The mesh carries normalized raw light readings; every response
curve and balance below is an art parameter editable live
without remeshing the world. Lateral skylight gives ambient visibility but not a hard sun
beam; the shadow map gates only the direct solar term. --- the deck's shadow --------------------------------------------
The sky draws a cumulus deck on a plane at a fixed world height,
and the ground under it should know. Following the sun up from
this point to that plane and asking the very same field the very
same coverage question is the whole of it: the deck's own shadow
sweeping across the world, which is what keeps a plain at noon
from being one flat sheet of light. The threshold is wider than
the sky's, because a shadow thrown from that far off is blurred
by the sun's own angular width long before it lands. #RSGLTL Occlusion bites harder than the raw mesh reading: the corner where
three blocks meet is what tells the eye these are solid volumes. --- what is not the sun ------------------------------------------
Everything above a face that is not the sun is still the sky, and
the sky is not one colour: a face turned up sees the zenith, a
face turned sideways sees the horizon, and a face turned down sees
what the ground bounced. One interpolation over the normal's own
vertical component is the whole of it, and it is the difference
between blocks standing in a world and blocks floating in a grey
room. The profile's ambient colour stays in the mixture: it is
the art direction's say over light the geometry cannot explain. A small floor keeps unlit geometry barely readable rather than
a void; caves stay dark for the right reason. The ambient term
is deliberately weaker, and the sun correspondingly stronger,
than a display-referred renderer could afford: the filmic curve
on presentation is what brings the sunlit half back down. --- the microfacet lobe ------------------------------------------
One GGX lobe off the shaped normal, with Smith's height-correlated
visibility and a dielectric's Fresnel: blocks are not metal, so
four per cent at normal incidence and the whole of it at a grazing
one. The roughness above decides everything about its shape, so a
polished face and a tufted one differ here without differing
anywhere else. Smith's height-correlated visibility already carries the
1/(4 cos cos) the microfacet specular would otherwise divide by. The cosine belongs in the specular lobe as much as in the diffuse
one: without it a highlight can appear on a face the sun cannot
reach, carrying whatever the shadow map happened to say there. The same sky again, in the direction the surface actually
reflects: the sheen that tells a smooth face from a rough one
out of the sun, and the one term that makes a block look like it
is standing under this weather rather than under a light bulb. Distant terrain fades into exactly the colour the sky's own ground
half arrives at, warmed where the ray runs toward a low sun: the
aerial perspective that puts a mile of air between here and the
horizon.define-shader-method shader-specification-for shaders.lisp:1218 ↗
Phase 3: one crystal proves emission and edit propagation #SKCRYS
*crystal-block* carries light emission 12, surface emission 1.2, and its own
atlas tile. Its surface-emission value makes the crystal face luminous; its
light-emission value seeds the propagated field around it. Those are related
but distinct facts, which is why the mesh carries material emission separately
from the blocklight level.
Add it to At pinned night, a crystal remains visibly emissive and warms neighboring
surfaces with a continuous cross-chunk falloff. Placing and removing it
converges to the reference field, rejects any stale mesh work already in
flight, and updates the visible neighborhood within a measured latency budget.*placeable-block-kinds* and make the key range and title derive
from the palette length instead of a fixed seven-material assumption. A
block removal or placement feeds the ordinary content-change path: direct sky,
blocklight, light publication, mesh invalidation, and GPU replacement all
complete without a special crystal edit command.Gate
Phase 4: HDR scene colour, tone mapping, and restrained bloom #SKHDR
Bloom is not “three easy passes” on the current target. First add an explicit linear HDR scene format (normally RGBA16F) to the GPU API/backend and render sky plus world into it. A live fullscreen post pipeline tone maps into an LDR sRGB presentation target which can be copied to the surface as today.
Then add half-resolution bright extraction, separable blur, and composite. The extraction can use HDR luminance and a threshold/knee expressed with the new shader math. If art direction later requires “emissive materials only,” add a named emission attachment and multiple render targets; do not overload scene alpha. All intermediate textures have explicit session ownership, resize handling, usage flags, and completion-safe destruction.
The first bloom target is deliberately the crystal at night. Exposure, threshold, radius, and intensity are live profile values, while the pinned smoke profile keeps deterministic output.
What the presentation stack became #IC14P3
The plan of #SKHDR is built, and the shape it took is worth recording because two of its decisions were made by the implementation rather than by the plan.
The scene attachment is :rgba16-float and every scene-stage pipeline targets
it: block surfaces, the sky triangle, world text, the terminal wall's glyph
and cell runs, the crosshair. Only the presentation image is an sRGB
eight-bit surface, and the hardware encode on that write is the only gamma
step in the frame. Fragments therefore write radiance, and a sun disc, a
specular glint, or a crystal is allowed to be many times display white.
Presentation runs a quarter-resolution lens chain between the scene pass and the tone map: a soft-knee bright pass with four scene taps per chain texel, a separable nine-tap gaussian expressed as five linear samples, and a radial sweep toward the solar disc. The chain ping-pongs between two attachments, so the blurred bloom ends in one and the shafts in the other, and presentation adds both into linear scene radiance before the fitted ACES curve, a small vignette, and the sRGB write.
Two departures from the plan:
- The four lens stages are four CLOS roles, not one shader with a mode lane.
:bloom-bright,:bloom-horizontal,:bloom-vertical, and:sun-shaftsshare a bind group shape and a fullscreen vertex stage and differ only in which fragment method they name, which is how the rest of #M3D7QK's live pipelines already work. - The shafts do not march the shadow map as #BAP0QU proposes. They sweep the already-blurred bright image toward the sun's screen position, because terrain which occludes the sun is already dark in that image. The cheaper effect arrived first; the marched version remains worth measuring against it.
Grading is art direction rather than architecture, so exposure, bloom gain,
threshold, shaft gain and decay, and vignette are ordinary special variables
that a SLY eval retunes on the running game without rebuilding a pipeline.

Mentioned in: Transplant the post stack onto the HDR path, Status: implementation notes and remaining plan, The shimmer that only noon could show, HDR crystal bloom
Referenced from code: The bloom and light-shaft chain runs on a quarter-resolution pair of
floating-point attachments. Bright fragments are extracted once, blurred
separably, and then swept radially away from the solar disc; presentation
adds both back into linear scene radiance before the filmic curve. Every
stage sees the same bind group shape -- source texture, linear sampler,
presentation uniforms -- so one layout serves the whole chain, and the
stage that reads it is chosen by CLOS role rather than by a mode lane.
#IC14P3 records the shape this stack took and where it left its plan. Four scene taps per chain texel. The chain is a quarter of the
frame's width, so a single tap would alias exactly the highlights
this pass exists to smear. The chain works in exposed units, so its contribution stays in
step with the scene when exposure moves.define-shader-method shader-specification-for shaders.lisp:2332 ↗
The screen centre is the focus plane #RLXR7B
Luvcraft chooses focus from the image itself: the presentation pass samples scene depth under the centre of the frame, then smoothly mixes only farther fragments toward a nine-tap tent blur. Near ground and the framed subject stay crisp; radial colour dispersion and a gentle vignette give the result lens character, while the HUD is drawn afterward and remains untouched.
That convention travels without taking the renderer with it. LUFT uses the same centre-relative rule but carries view distance in scene alpha, omits the bloom and shaft attachments, and owns its smaller lens pass independently. The shared shader language lets image mathematics cross between the two worlds while their resources and lifetimes remain honest.
A sky worth tone mapping #9SSXDJ
An HDR path only pays for itself if something in the frame is genuinely bright, and a two-colour gradient with a soft disc is not. The sky fragment stage became image mathematics over the view ray and the frame environment: a shaped vertical gradient between the profile's horizon and zenith colours, a warm low band and two Mie lobes around the sun, a fractal-noise cloud deck projected onto a plane at cloud height and drifting with an elapsed time the frame environment now carries, stars at night, and a solar disc drawn at a few times the sun's true angular radius and pushed far past display white so the bright pass has something real to feed on.
Only the last few degrees above level fade into the fog colour distant terrain fades to. The wide horizon blend the earlier gradient used washed the whole lower sky into haze, which reads as a rendering limitation rather than as weather.
The noise is lattice value noise, and its hash taught a lesson worth keeping: hashing the collapsed index x + 57y + 113z through a sine gives every plane of constant index the same value, and at star-field frequencies those planes are plainly visible as streaks across the sky. Folding a site's own components against each other has no preferred direction, and costs less than eight sines besides.
Mentioned in: Status: implementation notes and remaining plan, The sky became an atmosphere
Referenced from code: The sky is image mathematics over a view ray and the frame environment.
What it argues is that everything up there is one atmosphere seen at
different depths: rather than resolving in the first few degrees, because that is what
the optical depth along a ray actually does; belongs to the sun's own quarter of the compass, not all the way
round; is the whole glow around the sun; them farther out the closer it runs to level: thin cirrus far above,
and the cumulus sheet whose own shadow, sampled once along the deck
toward the sun, gives it a lit face and a dark underside; opposite the sun; chain has something real to bloom. Everything below the horizon arrives at the exact fog colour distant
terrain fades to, so silhouettes meet the sky without a seam, and only
then darkens -- gently, because a stronger falloff shows up as a step
where the last resident chunk ends. #9SSXDJ records why an HDR path
wants a sky with something genuinely bright in it, and #8X33G2 what each
of these terms replaced. One number decides how much of the sky is sunrise: a sun near the
horizon warms the haze, the scatter, and the cloud faces together. The sun's half of the sky, measured on the ground plane: a sunrise
band belongs in the east, and the west should stay blue. --- the atmosphere ---------------------------------------------
The vertical gradient is the sky's own colour at depth; a small
exponent spreads it across the whole hemisphere instead of
resolving it in the first few degrees, which is what an optical
depth along the ray actually does. A sunrise is sunlight that has come the long way through the
atmosphere, so the band it paints is the sun's own colour at that
hour, laid exactly where the ray runs both low and toward it. Away
from the sun's quarter of the compass the sky stays its own colour,
which is what keeps the west blue while the east burns. --- night ------------------------------------------------------ The galaxy is a great circle of the sky, so one fixed axis and the
ray's distance from its plane is the whole band. The moon rides opposite the sun, which puts it up for exactly the
hours the sun is not, and always full: the simple sky this world
wants, and the one that lights its nights. The component of the ray across the moon's own direction, in units
of its radius: the disc's face, which the maria are painted on. --- the cloud decks -------------------------------------------- The deck is a plane at a fixed height in the world, not a fixed
height above the camera: the ground has to be able to find the
same plane along the sun and read the same field there, or the
shadows sweeping over it would belong to some other sky. #RSGLTL Coverage is the profile's cloudiness against the knob; the edge
softens toward the horizon, where a deck's detail is far smaller
than a pixel and a hard edge could only shimmer. What lies between this piece of deck and the sun, sampled along
the deck itself: the shape's own shadow, and so its lit face. Silver lining: thin edges facing the sun glow, dense cores do not. After sunset a deck is lit by the sky alone, so it takes the
sky's own colour; a neutral grey at one twentieth reads warm
against a night zenith and the eye calls it dust. A deck recedes into the same haze the sky's own horizon does. --- the ground half --------------------------------------------
Below the horizon this shader stands in for terrain too far off to
be resident, so it must arrive at exactly the colour distant
terrain fades to. It may still say where the land is: the ray's
own meeting with the ground plane gives a point to sample, and a
wide, weak relief over it keeps the lower half from being one flat
wall of fog. The relief fades out at the horizon, where the plane
runs away faster than a pixel can resolve it. --- the sun ----------------------------------------------------
The disc is drawn at a few times the sun's true angular radius,
the way every game sun is, and deliberately far above display
white: the floating point attachment keeps it, the bright pass
feeds on it, and the filmic curve rolls it into a hot core
instead of a flat clipped patch. For a small angle the cosine
threshold of an angular radius is one less half its square. A star's disc is brightest at its centre; without the limb
darkening a sun drawn this large reads as a sticker.define-shader-method shader-specification-for shaders.lisp:2031 ↗
The sky became an atmosphere #8X33G2
#9SSXDJ gave the sky something worth tone mapping. What it drew was still a gradient with weather painted over it, and five of its terms were wrong in the same way: each was a fitted curve standing where a physical argument was available.
- The vertical gradient resolved inside the first few degrees above level, so seven eighths of the hemisphere was one flat zenith colour. A small exponent over the elevation spreads it across the whole sky, which is what an optical depth along the ray does.
- Two fitted powers stood in for the glow around the sun. One Henyey-Greenstein phase function evaluated at two asymmetries replaces both, and gets for free the thing the powers could not: the same medium gives a huge soft glow when the sun is low and a tight one when it is high.
- The warm band at sunrise ran all the way round the horizon. It is sunlight that came the long way through the atmosphere, so it is the sun's own colour at that hour, laid exactly where the ray runs both low and toward it. The east burns and the west stays blue.
- Stars were value noise raised to a power, which makes blobs the size of the lattice and smears them into streaks along its cell edges. A star is a point: hash the cell to a position, a magnitude, and a twinkling phase, and draw a small gaussian around it. Behind them is a galaxy band with dust lanes, and opposite the sun a moon, which puts one of them up for exactly the hours the other is not.
- Below the horizon the sky stands in for terrain too far off to be resident,
and did it with a flat wall of fog colour. It calls the same
aerial-perspective-colorthe block surface's fog calls now, so the two cannot drift apart, and it samples the ray's own meeting with the ground plane, so the lower half of the frame has land in it.
The cirrus deck above the cumulus is new, and so is the deck lighting: one
extra tap of the cloud field along the deck toward the sun is the shape's own
shadow, and therefore its lit face and its dark underside.
Mentioned in: Status: implementation notes and remaining plan, Materials and sky taken toward physical shading
Referenced from code: The colour something arbitrarily far away takes, seen along DIRECTION. Distant terrain and the sky's own ground half must agree exactly, or the
edge of the resident world draws itself as a line. They agree by asking
this: the profile's fog colour, warmed where the ray runs toward a low sun
and brightened by the same haze the sky's halo is made of, so a ridge in
the east at dawn glows and one in the west stays cool. #8X33G2 The sky is image mathematics over a view ray and the frame environment.
What it argues is that everything up there is one atmosphere seen at
different depths: rather than resolving in the first few degrees, because that is what
the optical depth along a ray actually does; belongs to the sun's own quarter of the compass, not all the way
round; is the whole glow around the sun; them farther out the closer it runs to level: thin cirrus far above,
and the cumulus sheet whose own shadow, sampled once along the deck
toward the sun, gives it a lit face and a dark underside; opposite the sun; chain has something real to bloom. Everything below the horizon arrives at the exact fog colour distant
terrain fades to, so silhouettes meet the sky without a seam, and only
then darkens -- gently, because a stronger falloff shows up as a step
where the last resident chunk ends. #9SSXDJ records why an HDR path
wants a sky with something genuinely bright in it, and #8X33G2 what each
of these terms replaced. One number decides how much of the sky is sunrise: a sun near the
horizon warms the haze, the scatter, and the cloud faces together. The sun's half of the sky, measured on the ground plane: a sunrise
band belongs in the east, and the west should stay blue. --- the atmosphere ---------------------------------------------
The vertical gradient is the sky's own colour at depth; a small
exponent spreads it across the whole hemisphere instead of
resolving it in the first few degrees, which is what an optical
depth along the ray actually does. A sunrise is sunlight that has come the long way through the
atmosphere, so the band it paints is the sun's own colour at that
hour, laid exactly where the ray runs both low and toward it. Away
from the sun's quarter of the compass the sky stays its own colour,
which is what keeps the west blue while the east burns. --- night ------------------------------------------------------ The galaxy is a great circle of the sky, so one fixed axis and the
ray's distance from its plane is the whole band. The moon rides opposite the sun, which puts it up for exactly the
hours the sun is not, and always full: the simple sky this world
wants, and the one that lights its nights. The component of the ray across the moon's own direction, in units
of its radius: the disc's face, which the maria are painted on. --- the cloud decks -------------------------------------------- The deck is a plane at a fixed height in the world, not a fixed
height above the camera: the ground has to be able to find the
same plane along the sun and read the same field there, or the
shadows sweeping over it would belong to some other sky. #RSGLTL Coverage is the profile's cloudiness against the knob; the edge
softens toward the horizon, where a deck's detail is far smaller
than a pixel and a hard edge could only shimmer. What lies between this piece of deck and the sun, sampled along
the deck itself: the shape's own shadow, and so its lit face. Silver lining: thin edges facing the sun glow, dense cores do not. After sunset a deck is lit by the sky alone, so it takes the
sky's own colour; a neutral grey at one twentieth reads warm
against a night zenith and the eye calls it dust. A deck recedes into the same haze the sky's own horizon does. --- the ground half --------------------------------------------
Below the horizon this shader stands in for terrain too far off to
be resident, so it must arrive at exactly the colour distant
terrain fades to. It may still say where the land is: the ray's
own meeting with the ground plane gives a point to sample, and a
wide, weak relief over it keeps the lower half from being one flat
wall of fog. The relief fades out at the horizon, where the plane
runs away faster than a pixel can resolve it. --- the sun ----------------------------------------------------
The disc is drawn at a few times the sun's true angular radius,
the way every game sun is, and deliberately far above display
white: the floating point attachment keeps it, the bright pass
feeds on it, and the filmic curve rolls it into a hot core
instead of a flat clipped patch. For a small angle the cosine
threshold of an angular radius is one less half its square. A star's disc is brightest at its centre; without the limb
darkening a sun drawn this large reads as a sticker.define-shader-function aerial-perspective-color shaders.lisp:1957 ↗
define-shader-method shader-specification-for shaders.lisp:2031 ↗
A cloud deck is a plane both halves of the renderer can find #RSGLTL
The cumulus deck used to be a plane a fixed distance above the camera, sampled in camera-relative coordinates: clouds at infinity, which is a perfectly good cheat until something on the ground wants to know about them.
It is a plane at a fixed height in the world now. The sky finds it along the view ray; the block material finds it by following the sun up from the fragment, and asks the very same field the very same coverage question there. The answer multiplies the direct sun only --- never the ambient, which is the sky the cloud is part of --- and its threshold is wider than the sky's, because a shadow thrown from that far off is blurred by the sun's own angular width long before it lands.
One fractal noise per fragment buys what no amount of material work could: a
plain at noon stops being one flat sheet of light, because the weather is
moving across it.
Mentioned in: Status: implementation notes and remaining plan, Materials and sky taken toward physical shading
Referenced from code: --- the shaped surface -------------------------------------------
A block face is flat geometry, but it should not read as a flat
material. Two shapings ride on the same face: a bevel that rounds
the last sixth of a cell toward its edge, and a per-texel relief
read straight out of the atlas the material is already painted in.
Both are pure normal perturbation, so no mesh changes. A block face's own normal selects its two in-plane world axes, so
one expression serves all six faces without a branch. The atlas coordinate and its screen derivative size both shapings.
The normal itself is already the central difference of the same
procedural relief field that paints the colour atlas. Texels per pixel: one cell is TILE-TEXELS of them. Both shapings are sub-block detail, so both have to fade out as
soon as they stop resolving on screen; otherwise distant terrain
sparkles and shows a lit grid instead of a surface. One measured
footprint drives both, at their own scales. The mesher's edge classification decides what each boundary does.
A convex edge rounds the surface over; a concave one fillets it
into the inner corner; a flush one -- the middle of an open plain,
where the face simply continues into an identically oriented
neighbour -- leaves the surface alone. Signs come straight from
the vertex lane, so the same ramp expression serves all three. A round-over is only legible if it is a few pixels wide. Held to
a fixed fraction of a cell it dwindles to nothing a short way off,
and every block in the middle distance flattens back into a card;
grown with the footprint it keeps its shape until it is genuinely
too small to resolve. The upper bound stops it from eating the
face it is supposed to be an edge of. An inner corner is a crevice, and a crevice gathers occlusion.
Only the filleted edges contribute; a rounded-over outer edge is
more exposed than the face it belongs to, not less. One linear texture read replaces the four height taps formerly
used here. The CPU generator clamps those taps within each tile,
normalizes the result, and stores the source height in alpha for
inspection. Fade the decoded tangent normal back toward the flat
face as its texels become sub-pixel. A real round-over is an arc, not a leaning plane. Adding a scaled
tangent to the face normal can only ever reach the arctangent of
that scale, so a strong edge needs an implausible scale and still
bunches its shading into the last sliver. Rotating the normal
through an angle instead sweeps it evenly from the face toward the
edge, which is what a fillet of that radius actually does. The
guarded denominator matters: over the flat middle of a face the
lean is exactly zero, and the angle is zero with it. --- the weathering -----------------------------------------------
The atlas has one tile per material and the world has thousands of
faces per tile, so on its own every grass block is the same card
seen again. Three fields fix that and none of them costs a texel:
a hash of the cell a face belongs to, constant across that face and
different on the next one; value noise over the world point itself,
which knows nothing about where faces begin and so drifts across a
whole plain the way real ground does; and a grain finer than a cell
whose own gradient leans the surface, so the ground is not merely
painted unevenly but lit unevenly. The cell is found by stepping
half a cell back along the face's own normal, which lands inside
the block whichever of the six faces this is. #3AVEKC Two more taps of the same grain, a step along each of the face's
own in-plane axes: their differences are the field's gradient
there, which is exactly the tilt a bump of it would give. Brighter patches are also warmer and duller ones cooler, and a
face's own constant drifts its hue as well as its value: a plain
of grass wants to be many greens, not one green at many
brightnesses. The grain is a cell-wide feature, so it survives to the distance
a cell does rather than the distance a texel does. --- the micro-surface --------------------------------------------
The atlas normal is this material's own slope at texel scale, and
the best evidence available about what happens below a texel: a
steep texel is a rough material. When the relief fades out with
distance its variance has to go somewhere, and the honest place is
the roughness -- Toksvig's argument -- so a highlight keeps its
size across the draw distance instead of sharpening into a
sparkle on every distant face at once. #2T8CCH A PCF tap displaced across a receiver plane must compare against
that plane's depth at the tap, not the center fragment depth.
With the orthographic shadow rows, the analytical UV depth gradient
is the face normal expressed in the light basis and scaled by the
world-span/depth-span ratio. Its denominator is bounded only near
grazing incidence, where direct sun is negligible. This prevents
wide kernels from reading the receiver's own slope as an occluder. A surface turned away from the light has no shadow decision to
make: its own geometry already excludes the sun, the receiver
plane's depth runs almost parallel to the light so every filter
tap lands a large and increasingly ill-conditioned distance away,
and the map's answer degenerates into noise. Near noon that is
every vertical face in the world at once, which is why the
shimmer was worst there. Fading the decision out toward "lit"
exactly where it stops meaning anything costs nothing visually --
the same cosine multiplies the direct term to zero -- and gives
the specular lobe and the diagnostic a stable value instead of
the noise. The lower edge is where the receiver-plane denominator above
starts being clamped: SHADOW-FORWARD is the negated sun, so that
dot product is exactly -N-DOT-L, and the clamp engages precisely
below 0.05. The two mechanisms therefore hand over rather than
overlap, and the clamp never has to carry a visible decision. The mesh carries normalized raw light readings; every response
curve and balance below is an art parameter editable live
without remeshing the world. Lateral skylight gives ambient visibility but not a hard sun
beam; the shadow map gates only the direct solar term. --- the deck's shadow --------------------------------------------
The sky draws a cumulus deck on a plane at a fixed world height,
and the ground under it should know. Following the sun up from
this point to that plane and asking the very same field the very
same coverage question is the whole of it: the deck's own shadow
sweeping across the world, which is what keeps a plain at noon
from being one flat sheet of light. The threshold is wider than
the sky's, because a shadow thrown from that far off is blurred
by the sun's own angular width long before it lands. #RSGLTL Occlusion bites harder than the raw mesh reading: the corner where
three blocks meet is what tells the eye these are solid volumes. --- what is not the sun ------------------------------------------
Everything above a face that is not the sun is still the sky, and
the sky is not one colour: a face turned up sees the zenith, a
face turned sideways sees the horizon, and a face turned down sees
what the ground bounced. One interpolation over the normal's own
vertical component is the whole of it, and it is the difference
between blocks standing in a world and blocks floating in a grey
room. The profile's ambient colour stays in the mixture: it is
the art direction's say over light the geometry cannot explain. A small floor keeps unlit geometry barely readable rather than
a void; caves stay dark for the right reason. The ambient term
is deliberately weaker, and the sun correspondingly stronger,
than a display-referred renderer could afford: the filmic curve
on presentation is what brings the sunlit half back down. --- the microfacet lobe ------------------------------------------
One GGX lobe off the shaped normal, with Smith's height-correlated
visibility and a dielectric's Fresnel: blocks are not metal, so
four per cent at normal incidence and the whole of it at a grazing
one. The roughness above decides everything about its shape, so a
polished face and a tufted one differ here without differing
anywhere else. Smith's height-correlated visibility already carries the
1/(4 cos cos) the microfacet specular would otherwise divide by. The cosine belongs in the specular lobe as much as in the diffuse
one: without it a highlight can appear on a face the sun cannot
reach, carrying whatever the shadow map happened to say there. The same sky again, in the direction the surface actually
reflects: the sheen that tells a smooth face from a rough one
out of the sun, and the one term that makes a block look like it
is standing under this weather rather than under a light bulb. Distant terrain fades into exactly the colour the sky's own ground
half arrives at, warmed where the ray runs toward a low sun: the
aerial perspective that puts a mile of air between here and the
horizon. The sky is image mathematics over a view ray and the frame environment.
What it argues is that everything up there is one atmosphere seen at
different depths: rather than resolving in the first few degrees, because that is what
the optical depth along a ray actually does; belongs to the sun's own quarter of the compass, not all the way
round; is the whole glow around the sun; them farther out the closer it runs to level: thin cirrus far above,
and the cumulus sheet whose own shadow, sampled once along the deck
toward the sun, gives it a lit face and a dark underside; opposite the sun; chain has something real to bloom. Everything below the horizon arrives at the exact fog colour distant
terrain fades to, so silhouettes meet the sky without a seam, and only
then darkens -- gently, because a stronger falloff shows up as a step
where the last resident chunk ends. #9SSXDJ records why an HDR path
wants a sky with something genuinely bright in it, and #8X33G2 what each
of these terms replaced. One number decides how much of the sky is sunrise: a sun near the
horizon warms the haze, the scatter, and the cloud faces together. The sun's half of the sky, measured on the ground plane: a sunrise
band belongs in the east, and the west should stay blue. --- the atmosphere ---------------------------------------------
The vertical gradient is the sky's own colour at depth; a small
exponent spreads it across the whole hemisphere instead of
resolving it in the first few degrees, which is what an optical
depth along the ray actually does. A sunrise is sunlight that has come the long way through the
atmosphere, so the band it paints is the sun's own colour at that
hour, laid exactly where the ray runs both low and toward it. Away
from the sun's quarter of the compass the sky stays its own colour,
which is what keeps the west blue while the east burns. --- night ------------------------------------------------------ The galaxy is a great circle of the sky, so one fixed axis and the
ray's distance from its plane is the whole band. The moon rides opposite the sun, which puts it up for exactly the
hours the sun is not, and always full: the simple sky this world
wants, and the one that lights its nights. The component of the ray across the moon's own direction, in units
of its radius: the disc's face, which the maria are painted on. --- the cloud decks -------------------------------------------- The deck is a plane at a fixed height in the world, not a fixed
height above the camera: the ground has to be able to find the
same plane along the sun and read the same field there, or the
shadows sweeping over it would belong to some other sky. #RSGLTL Coverage is the profile's cloudiness against the knob; the edge
softens toward the horizon, where a deck's detail is far smaller
than a pixel and a hard edge could only shimmer. What lies between this piece of deck and the sun, sampled along
the deck itself: the shape's own shadow, and so its lit face. Silver lining: thin edges facing the sun glow, dense cores do not. After sunset a deck is lit by the sky alone, so it takes the
sky's own colour; a neutral grey at one twentieth reads warm
against a night zenith and the eye calls it dust. A deck recedes into the same haze the sky's own horizon does. --- the ground half --------------------------------------------
Below the horizon this shader stands in for terrain too far off to
be resident, so it must arrive at exactly the colour distant
terrain fades to. It may still say where the land is: the ray's
own meeting with the ground plane gives a point to sample, and a
wide, weak relief over it keeps the lower half from being one flat
wall of fog. The relief fades out at the horizon, where the plane
runs away faster than a pixel can resolve it. --- the sun ----------------------------------------------------
The disc is drawn at a few times the sun's true angular radius,
the way every game sun is, and deliberately far above display
white: the floating point attachment keeps it, the bright pass
feeds on it, and the filmic curve rolls it into a hot core
instead of a flat clipped patch. For a small angle the cosine
threshold of an angular radius is one less half its square. A star's disc is brightest at its centre; without the limb
darkening a sun drawn this large reads as a sticker.define-shader-method shader-specification-for shaders.lisp:1218 ↗
define-shader-method shader-specification-for shaders.lisp:2031 ↗
Later: real-time shadows, not baked animated sunlight #SKLATR
If trees and terrain need shadows which rotate with the sun, add a GPU shadow map (or another explicitly evaluated real-time technique). Do not bake a directional term into chunk meshes and invalidate the whole resident world every time the clock moves. The voxel skylight field remains useful ambient visibility underneath that future pass.
The core shader language should stay mathematical. A word such as
shadow-visibility belongs in a shader-abstraction layer or in luvcraft
material vocabulary, where it can expand into ordinary operations: sample a
depth image, select a lane, compare receiver depth with a bias, and filter
neighboring taps when PCF becomes useful. This keeps the same abstraction
available to a directional sun, a spot torch, or an eventual point-light atlas
without making any one light shape a compiler primitive.
Other later capabilities should be earned by a concrete scene: coloured blocklight needs three propagated channels; translucent foliage needs opacity values between air and stone plus suitable material blending; clouds need a separate sky layer. None requires replacing the environment/derived-field split above.
Verification and observability #SKTEST
The strongest lighting test is differential: generate finite random worlds including multiple vertical chunks, apply random content edits and arrival/departure sequences, settle the incremental queues, and compare every resident sky/block value with a from-scratch solve using the same explicit boundary conditions.
Targeted tests cover:
- open columns, roofs, tunnels, shafts, overhangs, and partial opacity;
- blocklight falloff, removal, competing emitters, and alternate paths;
- all six chunk seams, including vertical seams;
- known-open, known-closed, and unknown residency boundaries;
- same-key chunk replacement before reconciliation;
- independent content/light revisions and stale snapshot rejection;
- corner interpolation without light leaks through two occluding sides;
- shader operator typing, imports, provenance, uniform ABI, and invalid-edit recovery.
Runtime counters exposed to SLY report lighting queue sizes and cell visits, candidate age/restarts, chunks and bytes touched, light publication latency, remesh latency, mesh bytes, and relevant frame CPU time. Performance claims come from those counters and repeated captures, not estimates in this page.
Visual review uses the actual moving renderer at pinned noon, dusk, and night: walk from sunlight into a cave, move under and around trees, cross residency edges, and place/remove a crystal on both sides of a chunk seam. The ordinary verification loop remains:
make test
make smoke
scripts/luv gazetteer build/gazetteer --view glow-floor
md5 -q build/luvcraft-smoke.pngEvery shader module added by a phase also passes spirv-val. A visual phase
changes the smoke hash deliberately only after inspecting the image; a fixed
clock then keeps the new hash stable.
Playable implementation sequence #SKPHAS
- Shader mathematics. Done: extended-instruction structure, typed math
operators, clamped existing fog, uniform-size validation,
spirv-val, and live replacement proof. - Sky. Done: session clock/profile, shared frame parameters, fullscreen live sky pipeline, animated material/fog, and pinned deterministic captures.
- Skylight. Done as a first CPU proof: light-field objects and revisions, explicit boundary/source semantics, reference and incremental solvers, snapshot light halo, raw vertex light, and caves/canopies visible in tests.
- Crystal. Emission/opacity block vocabulary, propagated blocklight, dynamic palette keys, and measured edit-to-photon behavior.
- HDR and bloom. Linear scene target, tone mapping, then a restrained crystal bloom with explicit resource lifetime and resize behavior.
- Real-time shadows. Done as a first credible pass: shader abstractions, separate GPU depth, stable projection, filtered receiver sampling, and isolated plus consecutive-play evidence. Animated light remains outside mesh products.
Each checkpoint is useful on its own. The sequence deliberately builds general shader and lighting capabilities where the feature needs them, while keeping the frame sequence, ownership boundaries, dense hot data, and live development behavior direct and inspectable.
Light work marks #WKLITE
These are the current work marks for the lighting path. They use the wiki's
work-mark convention #W3K9MK: each item is a figure with a small status,
intent, evidence, and done condition. Intent: make sky/block light a derived chunk field with its own revisions,
incremental reconciliation, raw mesh transport, and shader-side response
curves. Evidence: Done when: Intent: turn the test-only glow block into a real material the player can
select, place, remove, and inspect in motion. Evidence: Done when: a pinned-night capture shows the crystal face glowing and warming
nearby surfaces, while tests cover placement/removal and cross-chunk falloff. Intent: find out whether owner-thread relighting plus one mesh capture per
frame feels immediate enough during edit bursts and residency churn. Evidence to gather: Done when: the next scheduling decision is evidence-backed: keep owner-thread
lighting, tune frame limits, or move captured light work into the existing
production system. Intent: prepare the core shader language for shadow-map expressions without
adding a high-level Evidence: Done when: a shader can express the depth-read and receiver-depth comparison
pieces of shadow visibility using only core mathematical operators. Intent: add a source-expansion layer above the core shader operator set, then
define shadow vocabulary there instead of teaching the compiler a special
Evidence: Done when: shader authors can define reusable shadow source vocabulary that
compiles to the same mathematical expression graph as handwritten core shader
forms. Mentioned in: common.h is a library of judgment calls, Dimensioned types in the shader DSL Intent: make the renderer capable of producing a shadow depth map as a
separate GPU product owned by the luvcraft session, with light-space
projection data carried through the ordinary frame uniform. Evidence: Done when: the frame produces a stored shadow depth map from block geometry
without changing visible lighting yet; visible sampling moves to #MFBOFD. Mentioned in: Sun shafts are the highest-glamour transplant Intent: consume the shadow depth map from the block material using the
Evidence: Done when: a pinned scene shows moving geometry casting real-time direct-light
shadows through the shader abstraction, with all new textures, bind groups, and
pipelines retiring through the existing completion frontier. Mentioned in: Shadow-map producer pass and light projection resources Intent: keep shadow work from passing on screenshots that are merely plausible
or too dark to inspect. This was the first gate; #CH2CD0 records why it was
necessary but insufficient for representative-play acceptance. Evidence: Done when: future shadow iterations cite the pinned view and inspection
condition they used, not just the existence of a rendered PNG. Intent: replace the weak inference "the isolated yard contains a cast shadow,
therefore shadows are good" with separate, falsifiable evidence for shadow
geometry and for shadow behavior in the moving block world. Evidence: Done when: both the isolator and representative moving-view evidence are
credible, the documented CLI can reproduce them without hanging, and the
implementation has an explicit stable projection, filtering, and bias rule. Mentioned in: First isolated visual evidence standard for shadow changes, Moving-sun shadow shape and temporal evidence Intent: remove the review findings which made the shadow evidence or live
shader story less trustworthy than their documentation claimed. Evidence: Done when: escaped symbols survive the checker, abstraction edits reach a live
pipeline without sacrificing last-known-good behavior, every shader stage is
validated, and the public capture path exits cleanly on macOS. Intent: make rotating sunlight read as a smooth depth cue during the ten-minute
day without pretending that pinned-sun camera captures establish temporal
quality. Preserve truthful block occlusion, but prefer a round, contact-aware
filter footprint over visible square texel steps. Evidence: Done when: the light basis remains continuous through the old threshold; the
gazetteer can capture a fixed camera at explicit neighboring sun times; a
rounder, distance-aware filter and any resolution change pass shader validation
and are inspected in both the analytic yard and representative forest; and the
wiki records what each capture can and cannot prove. Mentioned in: Temporal derivatives and continuously filtered shadow decisions Intent: turn moving-shadow shimmer into inspectable temporal evidence, then
remove discrete nearest-sampled comparison flips without adding a whole-scene
temporal reconstruction system prematurely. Evidence: Done when: gazetteer capture can emit first and second temporal derivatives
plus numerical summaries; a 60-Hz-equivalent fixed-camera sun sequence makes
the shimmer measurable; the chosen sampling change reduces derivative chatter
without detaching contact shadows; and tests, SPIR-V validation, smoke, and the
wiki distinguish spatial comparison filtering from full temporal AA. Mentioned in: Receiver-plane shadow evidence and self-shadow stability Intent: explain and remove the constant frame-by-frame flicker which survived
#GKEHPF, and make temporal capture observe the shadow decision directly rather
than infer it from final colour. Evidence: Done when: a capture mode exposes shadow visibility without material and sky
confounds; the broad crawling receiver pattern is visible before and absent
after an explained geometric correction; normal and shadow-only derivatives
both improve substantially; contact remains attached; and tests, SPIR-V
validation, runtime capture, and smoke pass. Mentioned in: Temporal derivatives and continuously filtered shadow decisions, The shimmer that only noon could show Intent: #X9Q2YS removed the field-wide receiver-plane acne and left cast
shadows shimmering anyway. Play reported the residue as worst near noon,
which is a strong clue: a defect with a preferred hour has a mechanism that
depends on the sun's elevation. Evidence: The lesson worth keeping is about evidence rather than shadows: #X9Q2YS
settled on one explanation using captures pinned at 0.70, and its
measurements were honest. The hour it chose was the hour where the second
cause is weakest. A defect that play reports as time-dependent should be
measured across the day before it is explained. Done when: a frozen control proves the instrument; the diagnostic is
ungraded again; both causes are separately measured at the hour that shows
them; the day-long trade is stated rather than hidden; and contact remains
attached. Mentioned in: The shadow lattice turned about the world origin Referenced from code: The map's answer is taken only where the surface faces the light; a
surface turned away is lit by nothing, so it is also shadowed by
nothing. #0604PY Pack a texel-stable orthographic light-space transform as four vec4 rows. Returns the rows and the Texel snapping stabilizes the map against translation but can do
nothing about rotation, so the free choice of roll about the light
axis is worth making well. World up is a continuous reference --
the tilted orbit never reaches the Y pole -- but it is a badly
conditioned one: the sun's angle to it changes all day, the
transverse component collapses toward the orbit tilt as the sun
climbs, and the roll rate consequently peaks at noon, spinning the
whole texel grid under the world exactly when the sun is highest.
The sun's own axis of revolution keeps a constant angle to the sun
at every hour, so the basis it induces simply turns with the day.
#0604PY measures both the gain at noon and what it costs at dusk. Rotation still slides the lattice under the world, by the angle
times the distance from the pivot. Snapping the camera to a
lattice through the world origin put that pivot at the origin, so
a player standing a few hundred units out watched every shadow
edge vibrate a texel many times a second however slow the day.
The pivot is instead a persistent anchor that walks toward the
camera in whole texels of this frame's basis: the lattice never
shifts under translation, and it rotates about a point within a
texel of the eye. The anchor is kept in double precision so that
the walk does not itself wobble the lattice. #QWTQ6R Intent: play still reported every cast shadow flickering and jittering
whenever the clock ran, at any day length, and calm only once the clock
stopped. #0604PY had already chosen the best roll for the light basis; the
residue was too fast and too uniform to be roll. Evidence: What remains is the irreducible part: the lattice still turns about the
eye, so a shadow edge r units away sees a one-texel step every t / (r w)
seconds -- at ten metres and a ten-minute day, one every 0.6 s; at an hour
per day, one every 3.6 s. Below that lies the quantization every
rasterized shadow map has, a silhouette that can only move in texels. The
candidates from here are finer texels near the eye (a near cascade), a
wider minimum filter so one texel is a smaller fraction of the penumbra,
or a projection onto a fixed ground-plane lattice, which removes the
rotation altogether for receivers at the reference height at the cost of
redoing the receiver-plane gradient for a sheared basis. Done when: the frozen-camera, stepping-sun capture shows the penumbra
moving monotonically; SHADOW-LATTICE-TURNS-ABOUT-THE-CAMERA-NOT-THE-ORIGIN
holds a probe three units from an eye seven hundred units out to a
hundredth of a texel across one frame of sun; and walking still leaves
the footprint exactly still under sub-texel translation. Referenced from code: Pack a texel-stable orthographic light-space transform as four vec4 rows. Returns the rows and the Texel snapping stabilizes the map against translation but can do
nothing about rotation, so the free choice of roll about the light
axis is worth making well. World up is a continuous reference --
the tilted orbit never reaches the Y pole -- but it is a badly
conditioned one: the sun's angle to it changes all day, the
transverse component collapses toward the orbit tilt as the sun
climbs, and the roll rate consequently peaks at noon, spinning the
whole texel grid under the world exactly when the sun is highest.
The sun's own axis of revolution keeps a constant angle to the sun
at every hour, so the basis it induces simply turns with the day.
#0604PY measures both the gain at noon and what it costs at dusk. Rotation still slides the lattice under the world, by the angle
times the distance from the pivot. Snapping the camera to a
lattice through the world origin put that pivot at the origin, so
a player standing a few hundred units out watched every shadow
edge vibrate a texel many times a second however slow the day.
The pivot is instead a persistent anchor that walks toward the
camera in whole texels of this frame's basis: the lattice never
shifts under translation, and it rotates about a point within a
texel of the eye. The anchor is kept in double precision so that
the walk does not itself wobble the lattice. #QWTQ6R Intent: give emissive materials headroom and bloom without abusing alpha or
the current LDR scene target. Observed: the scene attachment is The lens attachments are session-owned and retire with the rest of the
session's GPU resources, but resize is not handled --- and was not handled
for the scene or depth attachments before this either, so the gap is
pre-existing rather than new. #YMAQQD carries it. Exposure, bloom gain, threshold, shaft gain and decay, and vignette are live
specials rather than sky-profile keyframes. A profile lane is the better home
once a keyframed value is actually wanted for time of day; nothing so far has
wanted one. Mentioned in: Exposure as a logarithmic level in the sky model, Sun shafts are the highest-glamour transplant, Transplant the post stack onto the HDR path, Dimensioned types in the shader DSL, Shadow credibility in representative play Intent: the world read flat because every face of a material was the same
card, the specular was a fitted power, everything that was not the sun was one
grey, and the sky's lower half was a wall of fog. Take each of those to a
physical argument instead: #3AVEKC, #2T8CCH, #8X33G2, #RSGLTL. Evidence: pinned captures at day fractions 0.28, 0.50, 0.72, 0.80, and 0.93,
from a ground stance and from a vista, against the same captures before the
change. What is left is art direction rather than architecture, and all of it is on
knobs: Intent: the scene colour, depth, presentation, and lens-chain attachments are
all created once from the canvas extent at session start. A resized window
therefore presents a stale-sized image. The bug predates the lens chain, but
the chain multiplies the number of textures which have to agree. Evidence: The ownership and publication conditions now hold in tests. The remaining
evidence is deliberately visual: resize a live game, capture it, and compare it
with a frame started at that extent. Until that confirms both presentation
size and the absence of stale attachments, this mark stays open. Mentioned in: HDR crystal bloomDONE First CPU voxel-light proof #WLDONE
luvcraft/light.lisp owns reference and incremental solvers.luvcraft/mesher.lisp snapshots a one-cell light halo and emits the
(sky block emission) attribute.luvcraft/light-tests.lisp checks reference equivalence, mesh light transport, and
same-key chunk replacement before reconciliation.:luvcraft tests pass and the wiki describes the implemented
Phase 0--2 state rather than the pre-implementation plan.DONE Player-placeable crystal #WLCRYS
luvcraft/blocks.lisp defines public *crystal-block* with atlas tile 9,
propagated light emission 12, and surface emission 1.2.luvcraft/app.lisp and luvcraft/render.lisp derive selection titles and
number-key bounds from placeable-block-kinds.luvcraft/tests.lisp covers palette slot 8, key selection, title text,
atlas size, and the crystal material readings.luvcraft/light-tests.lisp covers crystal placement/removal across a chunk boundary,
reference-field convergence, affected light revisions, and mesh emission.glow-floor view showed the crystal face glowing and warming
nearby floor surfaces.TODO Measure light-to-mesh latency #WLLATM
DONE Shadow-ready shader math substrate #H9Q1Q0
shadow primitive. The core vocabulary should remain
ordinary typed mathematics and resource sampling.2d22815 adds spv:step through the existing EQL operator protocol.shader-type records sample result and depth-image metadata; the expression
language accepts :depth-texture-2d as a typed resource.sample still lowers as ordinary OpImageSampleImplicitLod; depth samples
remain vec4 in valid SPIR-V and feed scalar depth math through swizzle.hal/shader/tests.lisp covers a depth-texture sample, swizzle :x, and step
as the comparison-shaped math a future shadow abstraction can expand to.make test and an explicit spirv-val --target-env vulkan1.0 check on the
depth-sampling fragment module passed.DONE Shader source abstractions for reusable shadow expressions #DXYHT4
shadow primitive.define-shader-abstraction adds an open EQL-method source vocabulary beside
the core define-shader-operator protocol; expand-shader-source-form
rewrites abstractions before ordinary expression parsing.shadow-depth-test is the one-sample abstraction over step and a biased
receiver depth. shadow-visibility is also source vocabulary, composing a
3x3 grid of those tests. Neither is a shader operator or a special lowering
primitive.hal/shader/tests.lisp proves the expanded graph lowers through
OpImageSampleImplicitLod plus the existing extended step path, that
abstraction redefinition affects fresh parses, and that a bad expansion
signals during parsing while already parsed graphs remain ordinary data.make test passed.DONE Shadow-map producer pass and light projection resources #2WX9PW
:vertex shader role directly,
so the shadow pass remains hot-reloadable through the same CLOS/MOP path as
visible materials.luvcraft owns a 1024x1024 :depth32-float shadow texture, view, sampler,
and :block-shadow pipeline, and encodes a shadow pass before the normal
sky/material/crosshair pass.block-world-shadow-vertex-specification projects mesh positions with dot
products against those rows.make test passed; an explicit spirv-val --target-env vulkan1.0 check on
build/block-world-shadow.vert.spv passed; make smoke rendered a hidden
frame successfully.DONE Sample the shadow map in the block material #MFBOFD
shadow-visibility abstraction, so moving sun light gets real direct shadows
while voxel skylight remains the ambient term.shadow-visibility; ambient skylight and blocklight keep their current
semantics.shadow-yard gazetteer view now isolates a
bright snow receiver, a tall stone pillar, a low bright sun, and a
high-contrast sky profile; a shadow-disabled control made the earlier
over-shadowing failure obvious.build/gazetteer/shadow-yard.png shows the pillar casting
a long, dark, pillar-shaped shadow across the receiver.make test, make smoke, and
scripts/luv gazetteer build/gazetteer --view shadow-yard passed.DONE First isolated visual evidence standard for shadow changes #0BHYRT
DONE Shadow credibility in representative play #CH2CD0
scripts/luv gazetteer build/gazetteer-review --view shadow-yard now exits
normally and reproduces the analytic 960x640 caster/receiver image. The
long pillar shadow keeps its expected direction and contact while the edge
is filtered instead of a raw depth interpolation.shadow-forest is generated terrain at 1512x982. The command
scripts/luv gazetteer build/gazetteer-review --view shadow-forest --count 4
--forward-step 0.2 captures four neighboring camera positions from one
live session. Inspection of frames 000--003 found the tree and terrain
shadows anchored across movement, with no projection jump or detached
contact. The bright snow remains clipped by the known LDR path; that is
evidence for #WLHDRB, not a shadow-map success claim.0.0015 * (1 - n-dot-l), replacing the coarse constant 0.003 bias.luvcraft/tests.lisp proves that a 0.01-world-unit camera translation
leaves the first two light-space rows unchanged while a 0.25 translation
crosses the snap grid. hal/shader/tests.lisp proves nine depth samples and
valid SPIR-V.DONE Tool and live-definition hardening after the shadow review #9250U7
scripts/luv gazetteer path completed for both single and
consecutive captures.|foo(bar|, foo\\(bar, an escaped bar, an unterminated bar, and an
ordinary repair. Reader-valid escaped symbols are not rewritten.shadow-visibility and observed (:installed t t): installed status,
advanced abstraction revision, and a replaced native pipeline.make shader-validate now emits and validates the shadow vertex module.
The generalized descriptor path no longer carries unused singular readers
or six obsolete sampled-image/sampler compatibility wrappers.make test, make smoke, strict repo-wide parinfer, and the documented
gazetteer paths pass.DONE Moving-sun shadow shape and temporal evidence #UDVPDW
shadow-frame-rows now uses world Y continuously. The tilted solar orbit
never reaches that pole, so the former abs(forward.y) 0.92 reference-axis
switch and its whole-map rotation are gone. A regression test compares the
neighboring orientations through that old threshold.shadow-visibility is a weighted 17-tap disk: a heavy
inner ring and half-sector-rotated outer ring avoid reinforcing a square
light-space border. Its two-to-six-texel radius grows from the center
depth's receiver/blocker separation, the 192-unit light-depth span, and the
sky profile's sun angular width.--day-start and --day-step. The command
scripts/luv gazetteer build/shadow-dusk-review --view shadow-forest --count 6
--forward-step 0 --yaw-step 0 --day-start 0.70 --day-step 1/600 inspected a
fixed camera at one-real-second-equivalent intervals in the ten-minute day.
The long tree shadow swept as a cohesive soft mass without a projection pop.
This sampling exposes gross discontinuities and changing silhouette shape;
it does not by itself prove sub-frame temporal smoothness at display cadence.shadow-yard capture retained pillar contact, direction, and a
readable far edge. make test passed with valid fragment SPIR-V and 17
explicit disk samples, and make smoke completed through the executable.DONE Temporal derivatives and continuously filtered shadow decisions #GKEHPF
sample-compare classifies the
four neighboring depths and Vulkan bilinearly filters their visibility;
ordinary nearest sampling remains available for the center blocker-distance
estimate. The shader language lowers this explicitly to
OpImageSampleDrefImplicitLod, and spirv-val accepts the fragment module.--difference-scale. They write amplified
grayscale first and second derivatives plus unscaled normalized mean,
maximum, and changed-pixel fractions in VIEW-temporal.csv. The first run
immediately falsified its own fixed-world premise: a large derivative spike
was terrain and trees publishing halfway through the sequence because the
ordinary screenshot threshold waits for only nine useful products. Temporal
capture now waits for every desired chunk product before frame zero.--day-start 0.70,
--day-step 1/36000, and no camera motion: one display-frame-equivalent step
for a ten-minute day. Against the otherwise identical nearest-comparison
sequence, mean first-derivative magnitude fell from 0.00011173 to 0.00009798
(12.3 percent), and peak changed-pixel fraction fell from 0.02354 to 0.01829
(22.3 percent). Mean second derivative rose slightly, from 0.00022243 to
0.00023217, while its peak changed area fell from 0.04544 to 0.04367.shadow-yard still has attached pillar contact and a coherent
penumbra.make test (including 17 comparison-sampled disk taps),
fragment SPIR-V validation, the 60-frame runtime capture, and make smoke
pass.DONE Receiver-plane shadow evidence and self-shadow stability #X9Q2YS
--shadow-only 1 uses the otherwise-unused fog-colour W lane to replace
block material output with direct-shadow. It preserves the exact runtime
shadow map, projection, sampler, kernel, and sun sequence while removing
albedo, ambient colour, fog, and most final 8-bit colour quantization as
confounds.DONE The shimmer that only noon could show #0604PY
--shadow-only diagnostic through ungraded.
Exposure, the filmic curve, the lens chain, and the vignette had all
arrived between #X9Q2YS and this work, and every one of them distorts the
quantity the diagnostic exists to measure. An instrument that shares a
pipeline with the picture needs saying so.sky-sun-orbit-axis) keeps a constant angle to the sun at every hour.
Noon fell further, to 0.00020979 and 0.02110: 82.2 and 76.0 percent below
the start.deftest shader-source-is-a-typed-clos-graph tests.lisp:367 ↗
defun shadow-frame-rows render.lisp:218 ↗
anchor to hand back next frame. The anchor is the
world point the light-space texel lattice is built around: it follows the
camera in whole texels of the current light basis, so camera translation
never moves the lattice by a fraction of a texel, and the sun's rotation
turns the lattice about the camera rather than about the world origin.
Without an anchor the camera position itself starts one.DONE The shadow lattice turned about the world origin #QWTQ6R
defun shadow-frame-rows render.lisp:218 ↗
anchor to hand back next frame. The anchor is the
world point the light-space texel lattice is built around: it follows the
camera in whole texels of the current light basis, so camera translation
never moves the lattice by a fraction of a texel, and the sun's rotation
turns the lattice about the camera rather than about the world origin.
Without an anchor the camera position itself starts one.DONE HDR crystal bloom #WLHDRB
: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 (#IC14P3). The
crystal-at-night capture in #IC14P3 shows the emissive blocks blooming into a
dark sky while the terminal wall beside them stays readable and the ground
they light stays in the toe of the curve rather than crushing. Scene alpha
was not commandeered; the relief channel #CHUKWD uses is normal-atlas alpha,
which is a different surface and is declared as what it holds.DONE Materials and sky taken toward physical shading #36LYCR
make test green throughout, including the strict parinfer pass,
SPIR-V validation of every stage, and the material and sky shape tests updated
to what the stages now say. The live canvas held sixty frames a second at
2688x1680 with the deck shadow's fractal noise in the block fragment stage.surface-detail, surface-roughness, specular-gain,
ambient-bounce, cloud-coverage, cloud-altitude, cloud-shadow-depth,
sky-scatter-gain, star-brightness, moon-radiance, and the grade's
saturation, contrast, and aberration.TODO Verify resized presentation in a live capture #YMAQQD
luvcraft-renderer now owns one frame-attachments cohort containing
the scene colour, presentation, depth, lens, crosshair, and cursor resources.
Resize builds and initializes a complete candidate first, discards frame-state
bindings keyed by the old attachments, publishes the cohort in one slot write,
then retires the old resources. Construction failure leaves the old cohort
installed; retirement failure leaves the new cohort installed and retains the
failed handle for teardown retry. Synthetic renderer tests observe only whole
attachment plists through the session readers and verify live-session migration
into the same owner.
Decisions now made; questions left to evidence #SKOPEN
Made here:
- shader-language growth is welcome and happens before the sky shader;
- frame-wide time/profile evaluation is CPU work, spatial image math is GPU work;
- light contributions add in linear space, with material emission separate;
- voxel light has its own data and revisions beside block content;
- absent residency and open sky are different states;
- the runtime algorithm is height-independent and checked against a simple full relight;
- opacity and emission are block semantics from the start;
- first storage is two u8 columns and first vertex transport is an explicit float vec3;
- directional sun shadows use a separate camera-centered, texel-snapped depth pass with a continuous light basis and round, blocker-distance-guided filtering; analytic, camera-motion, and explicit sun-motion captures provide distinct evidence;
- bloom waits for an HDR scene path and does not commandeer scene alpha;
- what the mesher knows about a face and the fragment stage cannot see travels in the vertex product, edges as well as light (#J19EBO);
- a material's micro-surface is generated arithmetic beside its colour, not an asset (#CHUKWD).
Questions for probes rather than prior debate:
- Does owner-thread incremental reconciliation stay within its measured frame budget during streaming and edit bursts, or should captured batches use the existing producer?
- Is the 48-byte vertex materially more expensive in the current scene, and which packed format would recover enough bandwidth to justify GPU-API work?
- Which sky profile and sun angular treatment look good in motion at the actual terrain scale?
- Does HDR bright extraction give the desired restrained bloom, or does art direction require a separate emission attachment?
- When larger vistas arrive, does one 128-world-unit orthographic footprint remain sufficient, or does measured play justify cascades or another representation?
Mentioned in: The atlas paints relief as well as colour
The maximum of compatible quantities.
The inner product of two vectors.
Produce dimensionless progress across compatible edges.
Multiplication and scalar scaling.
Addition over compatible quantities.
Interpolate compatible quantities by a scalar amount.
(block)BLOCK's own linear material radiance, independent of propagated light.
(tile x y)Return the 0..255 surface height of atlas tile TILE at tile-local X,Y. Like the colour, each named tile is one EQL method, so a live image can re-sculpt a single material's micro-surface and rebuild both atlases without touching the rest of the palette. See #CHUKWD for why relief and normals are generated beside…
(generic-function name specialized-lambda-list options &body body)Define a shader-producing method with ordinary DEFMETHOD identity. Calling the method reparses its small source form so changes to source-level abstractions participate in live rebuilding. Method replacement remains the role/stage identity watched by the MOP; abstraction revisions are tracked separately by live…
(role stage)Return the current durable shader specification for ROLE and STAGE.
Select and reorder vector components by a designator such as :XYZ or :RGB.
Expose a quantity's raw representation.
The componentwise absolute value of a raw value.
Subtraction or unary negation.
Constrain a quantity between compatible bounds.
Sample a two-dimensional texture through a sampler at a UV coordinate.
The componentwise square root of a raw value.
Division of two represented quantities.
The minimum of compatible quantities.
Hash one integer lattice site into the unit interval. Deliberately trigonometry-free, and deliberately a function of the whole site rather than of a collapsed scalar index. Hashing x + 57y + 113z through a sine makes every plane of constant index share a hash, and at star-field frequencies those planes are plainly…
Three octaves of value noise; the domain rotates to hide the lattice. The fold's state carries the rotating sample point and the running sum together, so the noise body is emitted once and executed three times rather than inlined three times.
Normalize a dimensionless vector.
State external meaning for a raw value.
Compare compatible quantities and produce dimensionless values.
Construct a meaningful literal.
Name a compatible derived quantity.
Four octaves of the same noise: a cloud deck's shape down to its wisps. A deck's silhouette is decided by the first octave and its edges by the last, so this is the one place in the sky worth paying for a fourth. The weights are the same halving series, renormalized, so the field still spans the unit interval and a…
Raise a dimensionless value to a dimensionless power.
The colour something arbitrarily far away takes, seen along DIRECTION. Distant terrain and the sky's own ground half must agree exactly, or the edge of the resident world draws itself as a line. They agree by asking this: the profile's fog colour, warmed where the ray runs toward a low sun and brightened by the same…
The Henyey-Greenstein phase function: how much light a haze sends on at COSINE off its original direction, for a medium of the given ASYMMETRY. One expression replaces the two fitted powers the sky used to brighten around the sun. A phase function's shape is the reason a low sun has a huge soft glow and a high one a…
One star per lattice cell of the sky, at a hashed place in its cell. Value noise raised to a power -- what the star field used to be -- makes smooth blobs the size of its own lattice, and they smear into streaks wherever the interpolation runs along a cell edge. A star is a point, so this hashes the cell to a…
(name parameters &body body)Define a reusable typed shader expression with ordinary source syntax. The body is parsed at each call site, so argument types and quantity meanings flow through the same operator protocol as handwritten shader expressions. LET* is lexical inside the function. The definition macro records source; its body does not…
(name specification)Test whether two compatible scalars are equal.
(binding)(expression)Reconstruct the compact mathematical form of EXPRESSION.
(left right)(form)Test whether one compatible scalar is greater than another.
(specification)(camera sky &optional anchor)Pack a texel-stable orthographic light-space transform as four vec4 rows. Returns the rows and the ANCHOR to hand back next frame. The anchor is the world point the light-space texel lattice is built around: it follows the camera in whole texels of the current light basis, so camera translation never moves the…
(x y z)(camera)(camera)(camera)(vector scale)(vector)()The axis the sun revolves around: world Z, because the orbit is a circle in the X/Y plane displaced along Z. Anything which needs a stable reference direction transverse to the sun should use this rather than world up. The sun's angle to this axis is the same at every hour, so a basis built from it is uniformly well…
(left right)Logical disjunction of tests and raw truth values.
(left right)(vector axis)#SKMESH establishes why light readings belong in the vertex product: they are what the mesher knows and the fragment stage does not. Face edges are the same kind of fact, and they turn out to be the difference between a block world which reads as carved solids and one which reads as a lit grid. A block face is flat…
The proposed vocabulary names structures that already work: The duplication is visible in current source rather than inferred from equal array lengths. light-region-locate remains the cold adapter from retained world coordinates, but propagation and unlighting retain an entry and local site, then use…
Intent: establish the smallest reusable spatial substrate beneath the current light and meshing products. Add heap-allocation-free decomposition, primitive local stepping with explicit boundary crossings, storage-order site enumeration, and face enumeration to chunk-domain or one narrowly related voxel-domain…
make-block-mesh-snapshot copies sky and block u8 columns for the same one-cell halo as block samples, after stable lighting publication. A sample-light-at generic is justified at this representation boundary: the live world/neighborhood and immutable snapshot each provide one method, while the dense inner…
The first version of #J19EBO's shaping added a scaled tangent to the face normal and normalized the sum. That can only ever reach the arctangent of the scale --- a strength of 0.42 buys 23 degrees --- so a legible edge needs an implausible scale, and even then the shading bunches into the last sliver of the ramp…
Made here: – shader-language growth is welcome and happens before the sky shader; – frame-wide time/profile evaluation is CPU work, spatial image math is GPU work; – light contributions add in linear space, with material emission separate; – voxel light has its own data and revisions beside block content; – absent…
shader-specification-for is an ordinary generic function specialized on a stable render role and stage. define-shader-method parses the typed graph once per method definition and returns that durable object cheaply. Evaluating the same role/stage method again uses normal CLOS replacement identity instead of…
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…
Some figures are also work marks: tiny roadmap entries that live in the same wiki page as the design they move forward. A work mark is an Org heading with: – a status keyword in the title: NEXT, TODO, WAIT, DONE, or IDEA; – a stable six-character ID property, so code, commits, and other pages can mention it as…
Intent: consume the shadow depth map from the block material using the shadow-visibility abstraction, so moving sun light gets real direct shadows while voxel skylight remains the ambient term. Evidence: – The Vulkan bind-group path now accepts multiple textures, samplers, and buffers in one layout, so luvcraft can…
Intent: replace the weak inference "the isolated yard contains a cast shadow, therefore shadows are good" with separate, falsifiable evidence for shadow geometry and for shadow behavior in the moving block world. Evidence: – scripts/luv gazetteer build/gazetteer-review --view shadow-yard now exits normally and…
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…
Intent: make rotating sunlight read as a smooth depth cue during the ten-minute day without pretending that pinned-sun camera captures establish temporal quality. Preserve truthful block occlusion, but prefer a round, contact-aware filter footprint over visible square texel steps. Evidence: – #CH2CD0 pins the sun at…
Intent: explain and remove the constant frame-by-frame flicker which survived #GKEHPF, and make temporal capture observe the shadow decision directly rather than infer it from final colour. Evidence: – --shadow-only 1 uses the otherwise-unused fog-colour W lane to replace block material output with direct-shadow. It…
Intent: turn moving-shadow shimmer into inspectable temporal evidence, then remove discrete nearest-sampled comparison flips without adding a whole-scene temporal reconstruction system prematurely. Evidence: – The 17-tap disk in #UDVPDW read nearest depth values and applied binary comparisons. Its weighted result…
Intent: the scene colour, depth, presentation, and lens-chain attachments are all created once from the canvas extent at session start. A resized window therefore presents a stale-sized image. The bug predates the lens chain, but the chain multiplies the number of textures which have to agree. Evidence:…
Colour is only half of what a material looks like. The other half is its micro-surface: whether it is granular, grooved, tufted, or faceted. luvcraft's atlas was already generated arithmetic rather than an asset, one paint-block-atlas-tile method per numbered tile, so the honest place for that half is the same…
The plan of #SKHDR is built, and the shape it took is worth recording because two of its decisions were made by the implementation rather than by the plan. The scene attachment is :rgba16-float and every scene-stage pipeline targets it: block surfaces, the sky triangle, world text, the terminal wall's glyph and cell…
The atlas has one tile per material and the world has thousands of faces per tile, so #CHUKWD's relief only made every grass block the same card seen again in better light. Three fields now make each face its own, and none of them costs a texel. A hash of the cell a face belongs to is constant across that face…
The block material's specular was one fitted power under a Fresnel weight. It is an ordinary microfacet lobe now --- GGX distribution, Smith's height-correlated visibility in the form that already carries the 1/(4\cos\theta_l\cos\theta_v), Schlick's Fresnel at a dielectric's four per cent --- which would be…
The cumulus deck used to be a plane a fixed distance above the camera, sampled in camera-relative coordinates: clouds at infinity, which is a perfectly good cheat until something on the ground wants to know about them. It is a plane at a fixed height in the world now. The sky finds it along the view ray; the block…
An HDR path only pays for itself if something in the frame is genuinely bright, and a two-colour gradient with a soft disc is not. The sky fragment stage became image mathematics over the view ray and the frame environment: a shaped vertical gradient between the profile's horizon and zenith colours, a warm low band…
#9SSXDJ gave the sky something worth tone mapping. What it drew was still a gradient with weather painted over it, and five of its terms were wrong in the same way: each was a fitted curve standing where a physical argument was available. – The vertical gradient resolved inside the first few degrees above level,…
Intent: #X9Q2YS removed the field-wide receiver-plane acne and left cast shadows shimmering anyway. Play reported the residue as worst near noon, which is a strong clue: a defect with a preferred hour has a mechanism that depends on the sun's elevation. Evidence: – A frozen-sun, frozen-camera control over 20 frames…
Intent: play still reported every cast shadow flickering and jittering whenever the clock ran, at any day length, and calm only once the clock stopped. #0604PY had already chosen the best roll for the light basis; the residue was too fast and too uniform to be roll. Evidence: – The live capture at day fraction 0.42,…
Lateral skylight gives ambient visibility but not a hard sun beam.