luv

Workshop wiki

luft-sites.org

The beveled surface is a sum over sites

This page records the offset construction that came out of the miter study (#2H7DL8, #GBCLEV) and is intended as the backbone of the next LUFT renderer. The short version: the beveled voxel surface decomposes exactly into face sites, crease-edge sites, and junction-vertex sites; every vertex of every piece is an intersection of offset lines and therefore an integer lattice point; junctions are fans of one equilateral tile counted by how far the bevel band turns; and Blender's Arc bevel realizes the same integers, so it serves as an executable oracle rather than a mood board.

Every vertex is an offset-line intersection #IQC0Y4

The sharp system's site map #2H7DL8 sends a site to one point G(s). The generalization that resolves the mixed junction of #DJK8HW is not a richer mystery object: it is the observation that every vertex the bevel ever needs is an intersection of offset lines — axis-aligned lattice lines displaced by the bevel offset m along one or two axes, intersected inside the flat face planes they share. Band rails, flare points, hexagon corners: each coordinate is (lattice coordinate) \pm\, m and nothing else ever appears.

With cell size a and integer m, every vertex of the entire surface is an integer triple. Watertightness becomes equality of integers rather than agreement to a floating-point tolerance, which retroactively addresses the oracle gap recorded in #7LEM72: closure can be tested exactly, and so can planarity (x{+}y{+}z constant across a candidate cap is an integer predicate). The chamfer width is w=\sqrt2\,m on the diagonal.

The choice luv is making: cells at a=8 lattice units with m=1 — an eighth-cell bevel, visually indistinguishable from the production 0.11-cell chamfer of #Z5NDTA — so that chunk-local vertex coordinates fit in small integers all the way into the shaders. This carries LUFT's packed-word idea (#RUAWR5) further down: not only the sites but the realized geometry lives on a lattice.

One tile counts every junction by its turning #EUZLR8

There is exactly one corner primitive: the equilateral triangle of side w lying in a body-diagonal \{111\} plane. A junction vertex is filled by a fan of k such tiles, where k is the turn the skirt boundary makes there, in units of 60°:

junctionkfill
convex or concave corner1the tile itself
mixed wall-end flare3isosceles trapezoid
diagonal staircase saddle6flat regular hexagon

The trapezoid has legs w, near base w, far base 2w — three tiles in a strip, which is half of a regular hexagon cut along its long diagonal. The saddle hexagon's vertices are the six permutations of (m,-m,0) around the vertex, all on the plane x{+}y{+}z=0: the regular hexagon section of the cube. Computed in the session and confirmed by the oracle of #WK4YU4.

Each tile vertex carries exactly ±30° of discrete Gaussian curvature. A chamfered cube corner pays its 90° deficit as three +30° quanta; the saddle pays −180° as six −30° quanta. Flat regions and band interiors carry nothing. The whole junction alphabet is ±30° curvature quanta arranged around the vertex according to its star, with tiles as the carriers.

The skirt is the atomic bevel operation #C2VXFE

What beveling does to one isolated flat face is: keep the face, emit a collar of four edge quads and four corner tiles. Every shape in this study is flat faces plus composed skirts. The skirt carries exactly 2π of curvature — four tiles × three vertices × 30° — which is Gauss–Bonnet made discrete: turning "up-facing" into "side-facing" costs a full turn, and the one annulus pays all of it while the face inside and the walls below stay flat. On the Gauss sphere the skirt populates precisely the \{110\} and \{111\} latitude ring between a face normal and its equator.

Stacking skirts is corner-cutting subdivision: the inner rail is a 4-gon, the outer an 8-gon, and each additional ring halves the corner angles — Chaikin's algorithm in 3D, whose limit is the smooth cupola. A skirt is one iteration of "round this edge". No skirt is ever emitted as a unit by the renderer of #8UGGY0, but it remains the right proof object: every face's skirt is covered by its incident sites' emissions, with shared vertices equal as integers.

Three plane families and one width dial #W5L4L3

Every face of the beveled world, at every offset, has its normal among the 6 axis directions \{100\}, the 12 edge diagonals \{110\}, or the 8 body diagonals \{111\} — 26 directions, fixed for all m. The bevel never invents a direction; it only moves area between the families. Dihedral angles never change as m grows: the beveled cell is the Minkowski sum of a cube of side a-2m and an octahedron of scale m, so the dial is a linear crossfade between two polytopes.

The endpoint m=a/2 is a critical point. Cell faces degenerate to 45° diamonds; on a unit staircase the convex chamfer through (x{+}z=c-m) and the concave fillet through (x{+}z=c-a+m) land in the same plane, so the whole slope fuses into one exact 45° ramp; and the fused diagonal plane is tiled by the trihexagonal (kagome) pattern — hexagons and triangles, the classic (111) section of cubic packing. Between the endpoints the axis-aligned faces act as spacers of width a-2m holding parallel diagonal planes apart; "suddenly everything becomes the slope" at the end is just the moment the spacer width reaches zero. For luvcraft this is a roundness dial with exact integer geometry at every stop: m=1 is blocks wearing chamfers, m=a/4 is blocks wearing architecture, m=a/2 is terrain with honest 45° hillsides where staircases are ramps.

Turn, don't die: sharp miters leave a (3,1,1) fingerprint #GT4Y9J

The policy decision the miter study was circling (#DJK8HW, #RJ0RLN) has a crisp algebraic form. Observed in Blender (cell 4, offset 1), sweeping every face normal of six voxel test objects against the 26-direction alphabet:

So the invariants of #IQC0Y4 and #W5L4L3 are not properties of beveling in general; they are properties of the turn-don't-die policy. Bands must turn through junctions as continuous loops — quad face loops whose poles are exiled to flat regions where coplanar triangles cost nothing — never terminate at a shared point. That is the design content the 4724734 experiment (#RJ0RLN) was missing: it moved points but left the topology face-owned and centre-fanned.

Blender's Arc bevel is an executable oracle #WK4YU4

Because all vertices are integers, Blender stops being a visual reference (#DJK8HW) and becomes a specification that can be diffed exactly: build a star as a voxel mesh, apply the bevel modifier (offset m, one segment, Arc outer miter), and compare vertex sets and face census against the tables. A first sweep of this kind ran during the session that produced this page:

Left to right: the fully beveled diagonal staircase fused into a kagome slope (m=a/2), the 2/1/0 junction star, the wall-end miter with Arc and with sharp outer miters side by side, the diagonal ziggurat with its saddle hexagons, the axis staircase with its coplanar nose bands, and the single cell wearing its skirt.

The census check: for the diagonal ziggurat (5×5 columns, height \max(0,4-(i{+}j)) cells), a twenty-line combinatorial site counter gives 60 exposed faces, 84 crease edges, 44 turning junctions; Blender's Arc bevel of the same voxels authors exactly 60 \{100\} faces, 84 \{110\} faces, and 44 \{111\} faces. The site decomposition and the modifier agree category by category, which is the independent geometric oracle that #7LEM72 demanded — exact, automated, and not a screenshot.

The Arc oracle has a manifold domain #TQ8FUN

The improvised scene established the attractive cases, but did not establish the domain on which Blender is a specification. The repository now contains a headless generator and its canonical mapping. It constructs the welded cubical boundary for every one of the 256 vertex stars, applies the recorded one-segment Arc modifier, extracts the central junction, and checks integer coordinates, the 26-normal alphabet, all 24 proper rotations, and occupancy complement. The corpus is regenerated and compared with:

make luft-blender-oracle-check

The exhaustive result splits exactly in half. All 128 regular stars — 12 proper-rotation classes whose boundary is edge-manifold and whose vertex link is connected — have integer vertices, only \{100\}/\{110\}/\{111\} normals, rotation-equivariant polygons, and identical unoriented geometry under occupancy complement. The other 128 stars are precisely the singular ones: 120 have a non-manifold boundary edge and eight join otherwise separate surface sheets only at the vertex. On those inputs Blender emits non-planar polygons, non-integer points, normals outside the alphabet, or results that change under a proper rotation. They remain in the mapping as counterexamples, not templates.

Blender's Arc bevel is therefore an exact independent oracle for regular cubical boundary stars, not a policy for singular chains. LUFT must either exclude those local occupancies from a terrain materialization, split their surface sheets before beveling, or state a separate singular-junction rule. The table of #WJRRK7 cannot silently assign Blender's arbitrary answer to them.

Faces, bands, and fans: three instance streams #8UGGY0

The renderer paradigm this fixes: the mesh is not a property of cells — a cell's relevant neighborhood is 26 cells, whose configuration space is astronomically large and whose geometry is shared anyway — it is a sum over sites, each drawn as an instance of a fixed template:

A record is chunk-local site coordinates plus a class byte; the vertex shader decodes class → offset table → lattice ± m, in integers. This keeps the refoundation's CPU/GPU boundary — the CPU classifies, the shader realizes — while redistributing the shape word of #RUAWR5: the edge stars move to the edge stream, the vertex stars to the vertex stream, and the face record shrinks to almost nothing. Junction geometry structurally cannot be triangulated into a face patch, which is the failure mode of #RJ0RLN made unrepresentable.

Classification visits every lattice vertex of a chunk once (17³ ≈ 5k byte-wise star reads for a 16³ chunk) but emission scales with surface: the ziggurat of #WK4YU4 is 144 quad instances plus 44 fans — roughly 350 triangles, every vertex an integer triple. The instance streams are literally the crease graph of the terrain with templates stamped on its elements.

Bands and fans carry material semantics #41HEJ1

Assigning shared geometry to one face's skirt forces arbitrary ownership conventions and loses information; the site decomposition keeps it. A band site structurally knows both of its faces, so it is the natural place for material blending — grass-top to dirt-side across the band's width, with a canonical rail-to-rail parameter. A fan knows all the faces around its vertex and blends them at corners. And the class id carries the semantic bit art direction wants most: convex bands are where wear, chipping, and edge highlights go; concave fillets are where dirt, moss, and occlusion accumulate. The creases stop being a rendering problem and become a material vocabulary — fitting, for a system that began as "make the edges look good".

Authored support geometry protects mixed bevel transitions #WSEK3C

The first material-width experiment makes the site's ownership concrete. Every packed surface stock compiles once to a terrain or architecture bit; each canonical lattice vertex ORs the bits of its incident stocks and chooses one width for every primitive that uses it. Terrain-only sites take width four, architecture-only sites width one, and genuinely mixed sites width two. This makes the contact width live without letting adjacent triangles disagree about their shared point.

Width four is the cubical medial limit. Many witness triangles collapse there deliberately, but a mixed field exposed one real topology defect: one collapsed triangle could leave a neighbour's long edge meeting two shorter edges. The variable-width evaluator now contracts that exact T-junction by retaining the collinear middle point in the surviving neighbour. In the five-cell regression luft.render:make-material-bevel-transition-study-scene, three unmatched edges become one repair and a closed surface. Its triangle quality is no worse than the width-one topology witness:

policytrianglesminimum anglemaximum aspectaspect over 5
mixed 1/2/41909.46°6.1716
width 12209.46°6.1740
width 222026.57°2.500
width 46835.26°2.120

Automatic transition geometry is still only the fallback. A modeler protects an important edge by authoring nearby solid, just as a support loop protects a classical deformation. luft.render::scene-builder-staircase therefore offers the sanctuary stair in three otherwise identical forms: open, a flat one-cell stone border, and a border raised one course as a low wall. These are ordinary scene cells and material placements; the mesher has no staircase case and still evaluates one width per site.

The production measurements show how local the intervention is:

stairwidth 1 siteswidth 2 siteswidth 4 sitesunmatched before contractionrepairstriangles
open7858100227739459153325512
flat border7877100827712462154325752
low wall7897100627704459153325834

The flat border merely relocates the mixed fans and reads as a fussy stepped edge in the clean plate. The low wall does something stronger: it keeps the width-one tread grid and its silhouette inside authored masonry, then moves the width-two transition to the wall's exterior contact with terrain. Its topological repair count is exactly the open baseline's. That is a promising division of labour: the automatic site rule closes arbitrary material fields; authored support geometry decides where an important architectural line should survive them. The sanctuary now authors the low wall by default; :open and :border remain named experimental variants under the same capture camera.

The capture set also has a small degeneracy atlas rather than asking a whole landscape to explain local topology. One isolated cell at widths one, two, and four makes the medial event literal. Widths one and two have the same 44-triangle account---12 face, 24 band, and 8 fan triangles---while their points move inward. At width four the faces and bands have zero area and are omitted; the eight corner-fan triangles survive as the medial octahedron. No zero-area primitive is retained.

The T-junction plate is smaller still. The evaluator reports the exact (left middle right) split created by a collapsed witness triangle, and surface-mesh-split-neighborhood retains only real output triangles incident to those points. Before contraction this is a three-triangle patch: one face owns the long left-right edge while two bands own left-middle and middle-right. After contraction it is four triangles because that face is split at middle. Both occupy the same geometric surface, which is why an ordinary wireframe can make the defect invisible; the topology-ink closeup shows the added diagonal without changing any point.

Finally, the singular-star plate keeps a distinct pathology distinct. Its #x06, #x18, and #x69 occupancies make cells meet only at an edge, a corner, or a parity junction. Those are not medial-width degeneracies. The star decomposition separates their incident sheets before bevel realization; the combined fixture reports ten singular stars and produces 352 nondegenerate triangles in a closed mesh. The clean and construction plates show both the apparent separated solids and the fan triangulation that makes that separation topologically honest.

defun surface-mesh-split-neighborhood mesh.lisp:2397
defunsurface-mesh-split-neighborhood
meshsplit

Return only the triangles incident to the three points in split.

split is (LEFT MIDDLE RIGHT), as reported in the :CANDIDATE-SPLITS bevel diagnostic. A triangle is retained when two or more of its vertices are split points, so the result is the smallest actual mesh patch that contrasts one long edge with its two short neighbours. Every retained edge is marked visible; no vertex or triangle geometry is otherwise changed. This is the executable closeup used by the mixed-bevel degeneracy atlas. #WSEK3C

check-typemeshsurface-mesh
unless
and
listpsplit
=3
lengthsplit
error"A mesh split neighborhood needs (LEFT MIDDLE RIGHT), not ~S."split
let
builder
%make-surface-mesh-builder
surface-mesh-domainmesh
surface-mesh-bevel-widthmesh
%map-surface-mesh-triangle-records
lambda
kindstockambientmasknormalabc
declare
ignoremask
when
>=
count-if
lambda
point
memberpointsplit:test#'equal
listabc
2
%emit-global-trianglebuilderkindstockambient#b111normal
listabc
mesh
defun scene-builder-staircase render.lisp:160
defunscene-builder-staircase
builderx0x1y0step-countbase-top&key
boundary:open

Author an ascending masonry stair and its optional support boundary.

boundary is a deliberately small modeling vocabulary. :OPEN emits only the treads, :BORDER adds a one-cell stone strip level with each tread, and :LOW-WALL raises that strip one course above it. The extra cells are ordinary authored solid and material input; the bevel mesher receives no special-case stair topology. #WSEK3C

check-typeboundary
member:open:border:low-wall
let
boundary-rise
ecaseboundary
:opennil
:border0
:low-wall1
loopforstepbelowstep-countforyfromy0fortop=
+base-topstep
do
scene-builder-boxbuilderx0x1yy0top:architecture-pt
whenboundary-rise
scene-builder-boxbuilder
1-x0
1-x0
yy0
+topboundary-rise
:architecture-pt
scene-builder-boxbuilder
1+x1
1+x1
yy0
+topboundary-rise
:architecture-pt
builder
defun make-manifold-spike-scene render.lisp:209
defunmake-manifold-spike-scene

Three isolated singular-star fixtures for the manifold-sheet spike.

The plots exercise an edge-touching pair, a corner-touching pair, and the four-sheet parity star. Nothing else in the scene can hide their junctions. #WSEK3C

let
builder
make-scene-builder:horizontal-bits6
labels
place-star
maskcentre-x
dotimes
when
logbitpsamplemask
scene-builder-cellbuilder
+centre-x
if
logbitp0sample
0-1
+10
if
logbitp1sample
0-1
+6
if
logbitp2sample
0-1
place-star#x0610
place-star#x1814
place-star#x6918
defun make-material-bevel-transition-study-scene render.lisp:456
defunmake-material-bevel-transition-study-scene

Build the five-cell medial T-junction regression. #WSEK3C

Two ascending architectural columns meet the corner of a two-cell terrain column. Widths one, two, and four occur in one tiny surface; the width-four medial collapse leaves exactly one long-edge/short-edge T-junction for the site-local contraction pass to resolve.

let
builder
make-scene-builder:horizontal-bits4
scene-builder-boxbuilder664423
scene-builder-cellbuilder542:architecture-pt
scene-builder-boxbuilder555523:architecture-pt

Coplanar merging is exact level of detail #YGP21F

Wherever a staircase recedes in both axes, flat \{111\} regions grow as triangles — the plane meets the three axes symmetrically, so its boundary against axis geometry runs along the 60° directions — and the construction is scale-covariant, so the same motif appears at cell scale, step scale, and terrain scale: a hierarchy of triangles, the octree showing through the geometry. Because coplanarity is an exact integer test, dissolving the interior edges of a coplanar region loses nothing: not a normal, not a silhouette, not a depth value. Distant terrain can render the merged triangle hierarchy while near terrain renders the full ornament, with no popping, because both meshes are the same surface. This is a compression pass over the streams of #8UGGY0, not an ownership decision in the representation.

The first streaming realization proved a narrower cubical tier: equal-stock faces became maximal rectangles, but bevel ornament disappeared at the tier boundary. The next pass now operates on the medial surface itself. It groups triangles by exact primitive plane, stock, ambient value, and stream; cancels shared edges; traces simple oriented boundaries; and ear-clips those boundaries while retaining every boundary vertex. A non-simple group falls back to its original triangles. All 256 vertex stars preserve closure, nondegeneracy, and an exact per-plane doubled-area signature against the unmerged rebuild oracle.

The first 512 by 512 highland sample is promising but names the CPU tradeoff honestly. One 84,794-cell outer chunk fell from 65,536 medial triangles to 18,466, a 71.8% reduction with identical plane signatures and the same 1,024 chunk-boundary edges. Meshing rose from 316 ms and 80 MB consed to 484 ms and 254 MB because this exploratory pass decodes and regroups the finished stream. That is already substantially cheaper to draw and upload; moving compression into site-stream construction is the next step if production latency matters.

defun %emit-greedy-planar-faces mesh.lisp:2633
defun%emit-greedy-planar-faces
builderfielddomaincellsstock-function

Merge exposed cubical faces into maximal coplanar rectangles. #YGP21F

Faces merge only when their axis, orientation, plane, and stock agree. The pass therefore changes neither position, normal, material, silhouette, nor depth relative to the cubical boundary; it only dissolves interior edges.

let
groups
make-hash-table:test#'equal
loopforcellacrosscellsdo
let
dotimes
axis-number3
dolist
side'
-11
let
x
svrefcoordinates0
y
svrefcoordinates1
z
svrefcoordinates2
caseaxis-number
0
incfxside
1
incfyside
t
incfzside
when
=0
%occupied-bitfielddomainxyz
let*
u
svref+axis-u+axis-number
v
svref+axis-v+axis-number
face
stock
funcallstock-functionface
plane
+
svrefcoordinatesaxis-number
if
pluspside
10
group-key
listaxis-numbersideplanestock
group
or
gethashgroup-keygroups
setf
gethashgroup-keygroups
make-hash-table:test#'eql
setf
gethash
%pack-planar-coordinate
svrefcoordinatesu
svrefcoordinatesv
group
t
let
group-keys
sort
loopforkeybeingthehash-keysofgroupscollectkey
#'%planar-face-group<
dolist
group-keygroup-keys
destructuring-bind
axis-numbersideplanestock
group-key
let*
u-axis
svref+axis-u+axis-number
v-axis
svref+axis-v+axis-number
group
gethashgroup-keygroups
nx
if
=axis-number0
side0
ny
if
=axis-number1
side0
nz
if
=axis-number2
side0
loopwhile
plusp
hash-table-countgroup
do
let*
first
loopforcoordinatebeingthehash-keysofgroupminimizecoordinate
u1
loopforufromu0whilefinally
v1
loopforvfrom
1+v0
while
loopforufromu0belowu1always
finally
loopforvfromv0belowv1do
loopforufromu0belowu1do
let
base
vector000
p0
vector000
p1
vector000
p2
vector000
p3
vector000
setf
svrefbaseaxis-number
plane
svrefbaseu-axis
u0
svrefbasev-axis
v0
flet
set-point
pointuv
setf
svrefpointaxis-number
svrefpointu-axis
svrefpointv-axis
set-pointp0u0v0
set-pointp1u1v0
set-pointp2u1v1
set-pointp3u0v1
%emit-quadbuilder:face
svrefbase0
svrefbase1
svrefbase2
p0p1p2p3nxnynzstock0

NEXT Implement the site-stream LUFT renderer backbone #RO2OCL

Intent. Rebuild LUFT's surface rendering on the decomposition of #8UGGY0: integer lattice cells (a=8, m=1 to start), three instance streams with fixed templates, CPU-side star classification into per-site records, and turn-don't-die junctions per #GT4Y9J.

Evidence. The offset construction and tile alphabet are verified at the computed sites (#EUZLR8) and empirically across six test objects by the Blender oracle (#WK4YU4), including the exact census match on the ziggurat. The sharp/Arc contrast (#GT4Y9J) shows what the current sharp system's collapse looks like algebraically and that the Arc policy stays inside the 26-normal integer alphabet.

Spike. The old face-patch ABI has been removed. The renderer now consumes one CPU-authored integer triangle mesh containing the three families above, through one uvec4 vertex stream and one uint32 index stream. The retained atelier, temporal resolve, camera, and material shader render the isolated 06, 18, and 69 singular fixtures; exact geometric edge counts close all three. The material-width pass now also evaluates widths one, two, and four on one canonical site field, contracts medial T-junctions exactly, and lets authored support geometry protect the sanctuary stair (#WSEK3C). Terrain and #Z5NDTA remain the acceptance boundary for this mark.

Done when. A chunk of luvcraft terrain renders through the three streams with every emitted vertex an integer triple; a closure check verifies shared vertices by integer equality across stream boundaries; the wall-end fixture of #Z5NDTA shows the trapezoid junction instead of the collapse of #DJK8HW, captured with the same construction-on and construction-off closeups as the sharp baseline (#L7N4MO) for side-by-side review.

TODO Derive the regular vertex-star table and choose singular topology #WJRRK7

Intent. Build the vertex-star table that #8UGGY0's fan stream reads: for each regular star, derive the incident bands, their offset-line intersections, the turn count k, and the tile fan entirely in integer arithmetic. Separately choose whether singular stars are rejected, split into regular surface sheets, or given their own topology; do not author their Blender accidents into the table.

Evidence. The turn-counting law of #EUZLR8 is verified at the convex corner, wall end, and saddle; the 2/1/0 star initially looked like an exception (a skew quad) but resolved to the standard trapezoid under the Arc policy (#GT4Y9J). The exhaustive external corpus now exists: all 128 regular stars in 12 rotation classes satisfy the integer, normal, rotation, and complement invariants, while all 128 singular stars make Blender leave at least one of them (#TQ8FUN). The oracle has established the regular domain; the remaining implementation work is LUFT's generator and singular policy.

Spike. LUFT now walks an occupied-side link graph and splits edge-touching and corner-touching occupied components into regular sheet cycles. The committed regular corpus realizes those cycles exactly, with executable coverage for 06, 18, and 69. A high-occupancy case such as 6f exposes the next representation boundary: one link cycle may revisit a radial direction through distinct topological copies, so it requires a covered junction rather than another ordinary eight-bit mask. The implementation signals that case instead of importing Blender's singular answer.

Done when. The table exists as data with its generating predicate in the repository; every regular entry is checked against the committed Blender Arc mapping by exact oriented polygon comparison; and every singular entry is rejected or decomposed by an explicit topological rule with its own executable tests and visual fixture.