The little block world and its possible worlds
A game-shaped path through the engine #B4K7WR
The little block world is the first luv experiment whose success is mostly felt rather than inferred. It opens a window, gives the player a place to look around, and makes changes in world representation, meshing, lighting, and physics visible immediately. That makes it an unusually good path through otherwise abstract engine questions.
This page is a map, not a specification. The aim is to preserve a sequence that stays fun while allowing the underlying ideas to become serious. A Minecraft-like game is useful as a recognizable constraint, but luv need not reproduce Minecraft's implementation or close off stranger worlds.
Mentioned in: The first animal is an entity, not a field
Referenced from code:defparameter *gameplay-smash-targets* gameplay-actions.lisp:8 ↗
The current proof #P3L8YX
The current implementation lives in luvcraft/, split between the
renderer-independent model in luvcraft/world.lisp and the visible slice
above it (blocks, terrain, meshing, simulation, and the application files):
- a
voxel-spacerecords a fixed chunk shape and explicit physical cell extent; - distinct world, chunk, and local coordinate values are related by checked Euclidean decomposition, including at negative coordinates;
- each resident
block-chunkowns a finitechunk-domainand one dense palette-indexed block-content column; - single-site world-coordinate access uses
world-block-at, while domain-shaped code borrows the chunk's palette and specialized index vector once throughwith-block-content-storage; - a
block-worldowns the resident chunk set and distinguishes resident air from an absent chunk, while chunk and world revisions make changes explicit; - a seeded
little-world-sourcedeterministically materializes a sliding nine by nine square of 16-cubed terrain chunks from multi-scale value noise, chooses grassy, sandy, snowy, and rocky surfaces from height, moisture, and local slope, then populates varied rocks and layered tree crowns after the whole neighborhood is resident; - a sparse edit overlay records explicit removals and placements separately from generated content, replays them after chunk rematerialization, and is checkpointed with the source description for later sessions;
- an
exposed-face-mesherwalks resident chunks on the CPU and emits only faces beside empty sites, under an explicit absent-neighbor policy; - each block kind selects faces from a small procedurally authored sRGB texture atlas; mesh vertices carry texture coordinates, corner ambient occlusion, geometric face normals, and raw light/material readings rather than baked colours;
- each resident chunk may publish a derived voxel light field with separate sky and blocklight arrays, light revisions, and boundary revisions;
- structured s-expression SPIR-V transforms those interleaved vertices, samples the atlas, applies animated sky, sun, blocklight, and material emission in linear space, and fades distant terrain into the sky;
- a lattice DDA ray stops distinctly at a hit, a resident miss, or absent terrain; an outlined Vulkan crosshair shows that ray, left and right clicks remove and place, middle click picks, number keys select placeable materials, and F takes out a phone with a shell on it (#S27JKR, #IK8PIN);
- a separate player AABB advances at fixed 120 Hz against the voxel lattice, with gravity, grounding, wall collision, acceleration, and jumping; the eye camera follows the body while relative pointer input controls its view and Shift raises the same controller's target speed for sprinting;
- a depth buffer, per-frame uniform buffers, and revision-driven per-chunk remeshing keep that inhabitable view on the real renderer; and
- renderer-native image readback writes a PNG without screen automation.
The whole slice remains live and inspectable:
This implementation is evidence that the renderer and lifecycle are ready for game-shaped work. The resident model is a first substrate, not a claim that residency, generation, edits, lighting, or derived products have become one finished system.
Referenced from code: Two authored play sequences through the ordinary centre-ray edit verb.
The wall and scaffold are merely deterministic terrain; every filmed
removal and placement still crosses defparameter *gameplay-smash-targets* gameplay-actions.lisp:8 ↗
luvcraft's player mutation, particle,
lighting, mesh-publication, and checkpoint-notification path. #B4K7WR #P3L8YX
The screenshot gazetteer names gameplay views #2SKLGB
The gazetteer is a small set of semantic screenshot descriptors. Each entry names a gameplay-ish view, builds or streams the world it needs, pins the sky clock, chooses a camera, places any animals it wants in shot, and captures through the real hidden SDL/Vulkan path. The views cover generated terrain at noon and dusk, a placed emitter on a dark floor, a crystal placed on a chunk seam, a yard of legible cast shadows, and a meadow of posed turtles (#KTRMAO). A capture never runs the frame simulation, which is what keeps it byte-deterministic and why the animals in a view arrive already placed rather than growing around a player.
scripts/luv gazetteer build/gazetteer
scripts/luv gazetteer build/gazetteer --view glow-floor --width 640 --height 480This is not a replacement for tests. It is a stable visual vocabulary for
asking whether a change still looks playable: the names are semantic, while
the PNGs remain generated artifacts under build/.
What the first extension establishes #E7N4ZS
The visible example keeps 81 resident chunks rather than one handwritten block array. Chunk residency is established first; smooth multi-scale value-noise terrain is materialized second; features are populated third; sparse player edits are replayed last. The seed and world coordinates are the generator inputs, so materializing the same region twice is idempotent and two worlds with the same seed agree site for site.
This is already a useful separation of concerns:
little-world-source + seed + sparse edits
|
v
resident chunk domains -- dense palette-index fields
| |
| edits | own + boundary revisions
| | derived light fields + revisions
v v
coalesced world revision ---> chunk CPU/GPU mesh productsA resident chunk notifies its owning world when its content actually changes,
including through the lower-level chunk-block-at writer. Assigning the same
value is a no-op. Bulk materialization nests world-change transactions, so
thousands of site writes advance the general world revision once while chunk
and residency revisions retain their finer meaning. Its dense inner loop
writes chunk-block-at-offset: this avoids per-cell coordinates and resident
world lookups without granting raw array mutation permission or losing those
revision consequences.
The API also makes the hot/cold boundary conspicuous. CLOS owns the meaningful
objects—world and source, chunk and domain, block-kind descriptor, mesher and
policy—and generic dispatch selects a representation at those boundaries.
The actual meshing pass then traverses a whole domain's dense unsigned index
column. Cells do not gain object identity merely because they are
addressable. world-block-at resolves a single world coordinate for
inspectors, sparse edits, ray interaction, collision probes, and other
genuinely row-shaped work; its appearance in a dense loop should look
suspicious in code review.
Each resident chunk has its own CPU mesh and GPU vertex buffer. Its dependency stamp contains the source chunk revision and light revision, plus only the opposing content and light boundary revisions of each resident neighbor. Thus an interior edit rebuilds one product, while a chunk-face edit or light-boundary change rebuilds the products that actually sample that face. The new pair is published together; the old GPU buffer is destroyed immediately at the API level, whose queue completion frontier defers native teardown if a submitted frame still owns its last use.
A checkpoint saves the world description, not its materialization #TR2JNQ
A luvcraft save is a readable, versioned s-expression containing the
block-world space, its procedural source, and the sparse authored edit
overlay. Block values use durable semantic names such as :stone and
:crystal rather than printed CLOS identities. Explicit air remains a value,
distinct from the absence of an edit. Resident chunks, light fields, meshes,
and GPU objects are omitted because the source and overlay can rematerialize
them.
The container format and the procedural source have separate versions. The first allows the file schema to evolve; the second prevents a changed terrain algorithm from silently presenting a different base world as the same saved one. Unknown versions and block names fail visibly rather than discarding meaning. Edits are sorted by absolute world coordinate, keeping the file stable under chunk residency, future materialization changes, and ordinary text diffs.
Saving crosses a small ownership boundary. The render owner captures an
immutable description after a successful edit and submits it without doing
file I/O. A sleeping latest-value worker coalesces checkpoints, prints to a
sibling temporary file, and atomically replaces the target. Orderly shutdown
submits the current player position, look direction, and selected material,
then joins the writer. Standalone luvcraft resumes an XDG data-directory
default or a path selected with --world FILE; screenshot and benchmark paths
remain ephemeral.
An append-only edit log remains a reasonable later storage policy. It would make each edit cheaper to durably append and retain useful history, but it also needs torn-tail recovery, event-version semantics, replay limits, and compaction. The current checkpoint is the smaller first proof. Its explicit owner-to-writer transfer is the seam at which edit events and periodic compact checkpoints can later coexist without moving disk I/O into a frame.
Mentioned in: Let the animals be part of the saved world, The sequence that stays fun, Questions to keep open
Referenced from code: Capture one immutable, printable checkpoint description. See #TR2JNQ.defun make-luvcraft-save-description persistence.lisp:241 ↗
DONE Persist and resume the authored little world #IY83CY
Intent. Let building survive quitting and restarting without treating resident chunks as canonical world storage or blocking a frame on file I/O.
Evidence. luvcraft/persistence.lisp implements the versioned description
protocol, validation, atomic replacement, and coalescing worker. The
standalone accepts --world FILE and checkpoints each successful edit plus
final resume state. The luvcraft suite round-trips negative-coordinate edits,
explicit air, source and resume state, rejects unknown meaning, and proves the
latest asynchronous request is flushed; all 41 tests pass. The standalone
executable also builds and reports the new option. The full make test path
passes, and make smoke retains MD5
226c4ffdd4515837d5c5a2d9fc415fa4.
Done when. A placed or removed block and the player's location survive a standalone restart, while rendering and derived chunk products remain absent from the file.
Chunk production is an ownership boundary #A8Y3WN
Generation and meshing are now ordinary asynchronous CPU production, not part
of rendering's compute budget. luvcraft/production.lisp owns a deliberately
small single-worker system built from SBCL's SB-CONCURRENCY:MAILBOX. A
blocking request mailbox lets the worker sleep without polling; the render
owner drains the result mailbox without blocking. A latest-value table keyed
by (:load chunk-key) or (:mesh chunk-key) supplies coalescing and priority,
so walking does not retain every superseded request. At most one completed
value waits for publication: an active request itself suppresses another wake
until its result has been received, bounding result memory when the window
pauses.
This is not a generally concurrent world. The canvas/render thread is the single writer of the resident hash table, chunk contents after publication, sparse edit journal, product table, and every Vulkan object. Worker inputs are immutable transfers:
- a load request contains the seed, chunk dimensions, deterministic landmark descriptions, sparse edits captured for that chunk, and a residency-demand token;
- a
block-mesh-snapshotcontains one denseu16target column, a one-cell halo column, a compact semantic palette, and its dependency stamp; - results contain either transferred chunk columns or a plain single-float mesh. They contain no live world, chunk, hash table, or GPU object.
The palettes intentionally retain shared CLOS block-kind descriptors. Those
objects are immutable presentable meanings; the repeated sites remain compact
indices and never acquire per-cell identity. Meshing dispatches once at the
snapshot/mesher boundary and then traverses aggregates.
Publication is optimistic. Every chunk residency receives a monotonically increasing incarnation; mesh stamps contain coordinate, incarnation, content revision, and only the relevant neighbor boundary incarnations/revisions. A returned mesh is accepted only if its stamp still equals the live stamp. A load is accepted only if its residency-demand token is still desired and the coordinate remains absent. Editing, eviction, rapid travel, and evict/reload therefore make obsolete work harmless rather than requiring cancellation in the worker's inner loop.
The frame still performs small bounded publication work. Its default limits schedule four loads, capture one mesh snapshot, and upload two completed products per frame. GPU buffer construction and writes stay there because the render thread owns the device. The old mesh remains renderable until a valid replacement is completely uploaded. These limits are policy knobs, while the ownership and validation rules are invariants.
Procedural little worlds own their moving desired window. A caller-supplied world without that source remains caller-owned: its current resident chunks are meshed asynchronously, but this production layer neither invents absent chunks nor evicts resident ones.
The first implementation uses one worker rather than a pool. This removes the large computation from rendering while keeping order and failure diagnosis intelligible and avoiding CPU oversubscription. It also does not make GC irrelevant: SBCL may stop all Lisp threads, so allocation-efficient aggregate meshing remains part of the latency story.
Shutdown is cooperative: the owner stops new scheduling, clears desired work,
sends :stop through the mailbox, joins the worker, and only then destroys GPU
destinations. Worker conditions cross back as structured production results
and are retained on the demo instead of silently killing the producer.
The square is now a camera-centered resident window rather than the whole world. When the player crosses a chunk boundary, one deterministic strip is materialized and populated, sparse edits are replayed, and the opposite strip is evicted in one coalesced world transaction. The next mesh refresh builds products for the entering chunks and destroys the leaving buffers through the GPU completion frontier. Thus travel is not bounded by the original square, while resident world data and render products remain bounded at 81 chunks.
The tests keep these layers visible. :luvcraft/world exercises coordinates,
domains, palette storage, revisions, residency, and ray traversal without SDL
or Vulkan. :luvcraft adds deterministic generation, cross-chunk face
culling, and camera-directed remove/place behavior.
Landmarks from working voxel engines #V4K8LN
Several established engines converge on the same boundaries without sharing one representation:
- Luanti's basic data structures distinguish an
airnode fromignore, which means unloaded or ungenerated. A 16-cubed MapBlock serializes node type and parameter data in bulk while metadata, timers, objects, and the client mesh remain separate concerns. - Voxel Tools' overview and streaming model use independently represented channels, commonly in 16-cubed blocks. Uniform channels need no dense allocation, and streams load and save blocks independently from deterministic generators.
- Veloren's generic Chunk divides storage into small groups; a missing group is implicitly the chunk's default voxel, while occupied groups use compact indices into voxel values. It can therefore remain sparse without making the public voxel value itself a storage address.
- Minecraft Bedrock's official world-generation overview describes generation as staged construction from a seed: terrain, biomes, structures, features, lighting, and final updates. That is evidence for treating generation as a source of materializations rather than as the chunk container itself.
The shared lesson is not “all voxel worlds use 16-cubed arrays.” It is that coordinate spaces, resident bulk values, missingness, exceptional metadata, generation, and derived render products are different things. Compression can then vary behind the logical field interface.
A world is not necessarily its finest materialization #W8D2MT
It is tempting to say that a block world is a three-dimensional array of block objects. That description becomes strained as soon as the world is larger than memory, generated on demand, edited sparsely, or represented at more than one resolution.
A more general reading is:
world description
+ procedural sources
+ persistent edits
+ materialization policy
|
+-- resident finite domains and their bundles
+-- derived meshes, colliders, and navigation products
`-- absent, loading, stale, and evicted regionsAt full resolution, one site may have a categorical block state. A coarse site may instead carry surface bounds, occupancy, dominant material, material coverage, or an approximation error. There need not be a one-to-one coarse "block object." What remains stable is that a finite domain identifies sites and semantic fields have values over those sites.
The eager current resident set is then one finite materialization of a particularly small description, not the definition of worldhood.
Mentioned in: Sleep is residency, and it prices determinism
Things with identity and values at sites #F6R9QA
CLOS does not require one heap object per cell. It helps express which things really have identity, ownership, behaviour, and independently inspectable lifetimes.
Likely objects include:
- a world source, composition, or edit overlay;
- a finite domain and a resident chunk over it;
- a field specification and its storage representation;
- a block kind or categorical block state shared by many sites;
- a generator, materializer, mesher, lighting rule, or residency policy;
- a derived mesh or collider with a source version; and
- genuinely individuated entities such as a player, creature, dropped item, or chest inventory.
A site in a dense domain is usually not such an object. It is an address at which one or more fields can be read. A temporary row or focus object may make that site pleasant to inspect without allocating a permanent object for every cell. #C9L2WA develops this distinction.
Mentioned in: The first animal is an entity, not a field
Block kinds, states, and exceptional entities #K5T2VF
The existing block-kind instances behave like palette entries: many sites
share one object that owns colour and generic behaviour. A chunk now stores
compact unsigned 16-bit indices into a chunk-local vector of those semantic
objects. world-block-at returns the shared object after
decoding, while a whole-column algorithm borrows the contiguous index array
through with-block-content-storage.
A useful distinction is:
- a block kind says what grass, stone, or wood generally is;
- a block state adds categorical variation such as orientation, age, or an open/closed state;
- a site value selects a block state for one domain site; and
- a block entity owns sparse exceptional state that is not sensible as a dense field, such as an inventory or program.
Solidity, opacity, and ordinary face material may be derived from a palette entry rather than copied into fields. Lighting, fluids, or ecological readings may deserve separate fields when algorithms actually consume them.
Mentioned in: Let the animals be part of the saved world
The first animal is an entity, not a field #KTRMAO
A turtle is the first thing in the little world with a life of its own, and it is built the way #F6R9QA says such a thing should be: a CLOS instance with a position, a heading, a small mind, and an independently inspectable lifetime, rather than a reading of a dense field over sites. There are a handful of them alive at a time, so identity costs nothing. What stays dense is what is always dense here — the per-frame render product, one interleaved vertex array over the ordinary block atlas and block surface pipeline, like the smash fragments before it.
Three protocols meet in an animal, kept apart because they change for different reasons:
advance-critteris its behaviour, and the only part a new species really has to think about. A turtle walks a while, rests a while, turns away from what it bumps into, and never jumps. Nothing it decides comes from a random state: its choices are hashed from its seed and the number of choices it has already made, the same discipline the terrain source and the fragment bursts keep, so the same animal in the same world wanders the same way on every machine and in every replay.- the body protocol —
body-position,body-half-width,move-body-axis— is its contact with terrain, and it is the player controller's own one-axis-at-a-time AABB sweep with the player factored out of it. Item 5 of #S2K5HN said that scalar reference simulation was the behaviour later dense body and contact domains must preserve; an animal is the second participant, and the first evidence that the protocol was about bodies rather than about the player. map-critter-boxesis its appearance: a handful of textured boxes in the animal's own frame, which the emitter turns by the animal's yaw. A model never sees world coordinates.
Its hide is painted by the same per-tile arithmetic as stone and moss —
carapace scutes, plastron plates, and pebbled skin are three more
paint-block-atlas-tile and paint-block-atlas-relief methods. One thing a
terrain material never needs: a block face takes a whole tile because it is a
whole cell, while an animal's faces are each a different fraction of a cell,
so a critter face takes the centred fraction of its tile which keeps texels
roughly the same size everywhere on the animal. Without that, a shell wears
scutes the size of itself and smears the same scutes into bands around its
rim.
An animal also answers a ray for itself. The lattice traversal of #B4K7WR is no use here, because a critter is not on the lattice; what the crosshair asks is whether the ray enters the same upright box the animal's feet walk in, and whichever of the animal and the terrain the ray reaches first is what the player is looking at.
Mentioned in: The screenshot gazetteer names gameplay views, The sequence that stays fun
Riding is a focus that moves #W2E5HR
Mounting a turtle is the same session transition as reading a wall terminal (#8JCMA5), and it is meant to feel like one: TAB enters the interaction with whatever the crosshair is on, shift-TAB leaves it, ordinary player input stands down for the duration, and the camera eases out of wherever it was into the pose the interaction asks for — the same easing, at the same rate, into the same narrowed field of view.
Two things distinguish a mount from a wall, and each is one generic function:
- Its pose moves, and it is a seat rather than a distance: the pose
luvcraft-focus-camera-posereturns is a place on the animal's own back, low over its shell, so the rider is on the animal instead of watching it from behind. Being asked again every frame is what makes the ride feel like the animal's gait and the animal's own slow turns;critter-swayadds what the pose cannot carry, the rock and roll of a walking body, as a small lift and a small yaw. Where the rider sits is art direction, settled by looking at screenshots, so the three numbers are parameters rather than constants. - It carries the player.
luvcraft-focus-carries-player-panswers true, the scalar controller stands down completely, andadvance-luvcraft-focussets the player's body on the animal each frame. That is what keeps chunk residency, checkpoints, and the return camera all following the ride rather than the place it started from.
A ridden animal is reined rather than driven. urge-critter hands the
animal the rider's wishes as bounded values in -1..1 and the animal makes of
them what it can, which for a turtle is a slow walk and a slower turn; the
wanted heading is held within a bounded lead of the animal's own yaw, so a
held rein cannot run ahead of what the animal is able to follow. Getting off
is a small search for room, because an animal can walk under a ledge its
rider could not stand under.
Mentioned in: The sequence that stays fun Intent. A checkpoint currently saves the world description and the player
(#TR2JNQ) and says nothing about who lives in it, so every session grows a
fresh population around the player and yesterday's turtle is not the one you
meet today. An animal with an inspectable lifetime should have a lifetime
longer than a process. Evidence. The population is small, sparse, and already deterministic given
its seed, which makes it exactly the sparse exceptional state #K5T2VF
describes rather than a field: a save entry per living animal, naming its
species, position, heading, and seed. The open question is what the world
description owes an animal that has wandered into a chunk which is no longer
resident. Done when. Riding a turtle, checkpointing, restarting, and finding the
same animal where it was left, with a test that round-trips a population
through a save description.TODO Let the animals be part of the saved world #L4OMIZ
The player has a body, and the body holds things #S27JKR
Until now the player was a camera: an eye at a height above a collision
box, with an inventory of materials and nothing to carry them in. The
body in body.lisp gives the eye two arms.
They hang into the bottom corners of the view, sleeve and wrist and hand,
but are no longer little boxes. player-body owns a live ray-marched SDF
pipeline like the embodied gnome and cat: capsules make the forearms,
ellipsoids make palms and thumbs, and smooth unions make the wrist one
silhouette. Five player-group knobs expose arm radius, hand size, sleeve
length, shoulder spread, and smoothing; the thumb derives from hand size so
turning one knob does not pull the figure apart.
The complete neutral avatar is authored as the same analytic field -- head,
torso, legs, arms, and hands -- while first person launches rays only through
its lower-screen, view-local arm slice. In that frame x is camera-right, y is
up, z is forward, and the eye is the origin, so hands stay attached to the
body while the head looks around. Walking bobs their published palm points on
a phase accumulated from ground speed, so standing still stands still, and a
breath moves them when nothing else does. The explicit :viewmodel stage
draws after every world participant and before held-item geometry, rather than
letting a later-attached agent blend over the player's own hands. A future
third-person or reflected view can launch rays through the full avatar instead
of inventing another body.
A hand can hold something. The held-item protocol is a few generic functions -- map-hand-item-boxes for the item's boxes in the grip frame, hand-item-carry-pose for where the right hand holds it, hand-item-name for the title, hand-item-taken-out and hand-item-put-away for what the item does about being held -- and the body eases between pocket and hand rather than cutting, so taking a thing out is a motion. Held-item geometry draws after the analytic hands in the same grip frame; the phone can remain a round-corner slab with a live display without dictating how flesh is shaped. F takes the phone out or puts it away; a pocketed item keeps its state.
Mentioned in: The current proof The first thing to hold is a phone, because it is funny: a slab of
near-black glass a little too large for the hand, held up in front of the
face the way everyone holds theirs, and on it a live shell. It is not a
new kind of screen. phone.lisp builds
the very display TERMINAL-WALL.LISP builds for a wall (#8JCMA5) --
Ghostty terminal, PTY, Slug glyph run, cell backgrounds, screen panel,
faceplate glass -- on a surface of its own kind. The trick is where that surface lives. A wall's surface is a rectangle of
blocks in world space. The phone's is a rectangle in the grip frame, the
right hand's own coordinate space, where the screen never moves. What
moves is the hand, and therefore, seen from the phone, the camera: each
frame the display draws against a frame uniform whose camera lanes are the
real camera re-expressed in grip coordinates. Nothing about the runs is
rebuilt for motion. The same glyph instances that were right last frame
are right this frame, because in their own space nothing happened. The
projection and near and far planes are the real ones, so depth is honest
against the scene and the phone's own slab. This took only making the
surface a protocol (terminal-surface-axes, the lower-left point, physical
width and height, currency, focus score, focus camera pose) and letting a
display say which uniform it draws against. Focus is the same transition as a wall's: TAB with the phone out enters
its shell and keys go to the PTY; shift-TAB leaves. The phone offers no
camera pose of its own, so the camera stays where the player left it and
only the field of view narrows; the hand answers instead, easing the phone
square to the centre of the view and a little further off, where the
narrowed view frames the whole screen. Putting the phone away pockets it
with its shell still running. The phone is not a tube, and its glass says so. The wall's screen and
faceplate materials are the whole analogue argument of a CRT -- bezel in
world cells, raster, hum, grain, a convex bulge -- and on a hand-sized
slab every one of those reads as a fault. The phone draws the same two
panels with materials of its own ( The wall's three modes -- shell, film, browser -- are not what the phone
inherited; it is a shell and only a shell. That mode switch is a kludge
waiting to be redesigned from the ground up, and when it is, the phone
should be one of the things it is redesigned for. Mentioned in: The current proofThe phone is a wall terminal you can carry #IK8PIN
:phone-screen, :phone-glass): a flat
black panel behind the text with rounded corners resolved analytically,
and coated glass in front that only reflects -- Fresnel sky, one tight
glint -- plus fingerprints, patches of oil that scatter a little of the
room forward where the clean coating would not, so they show only where
there is light to scatter and never as paint. The slab itself is one
even matte tone with the least relief a tile is allowed; its corners are
a real mesh -- two fans and a rim of quads around a rounded-rectangle
outline (emit-rounded-slab), the one thing in the body that is not a box. The grip takes the
sides, never the screen. The screen is proportioned for a real terminal:
thirty-five rows of Input Mono Condensed (from the user's own fonts when
installed, the wall's Monaspace otherwise) give sixty columns, on a slab
proportioned a little like a small handheld console. Carried, it hangs at
arm's length and low, tipped back to be glanced at; focused, it comes up
square and close.
The sequence that stays fun #S2K5HN
The order below is meant to keep each architectural proof attached to a new experience:
- Look around a little world. Done: the camera inhabits generated terrain.
- Touch it. Done as a first correctness pass: ray traversal selects, removes, and places blocks, and revisions make the edit visible to the renderer.
- Walk into another chunk. Done: the 81-chunk materialization follows the player, deterministically generating an entering strip and evicting the opposite strip. Chunk-owned CPU/GPU products arrive and retire with it.
- Let it glow. The sky and voxel-light proof is done: animated sky parameters, block-face atlas selection, procedural texels, geometric normals, corner occlusion, derived sky/blocklight fields, raw vertex light, material emission input, a player-placeable glowing crystal, and longer distance fog all run through the real Vulkan path.
- Collide with it. Done as a playable scalar reference: a distinct player body walks, falls, grounds, stops at voxel faces, and jumps under a fixed step. The voxel lattice itself is the static acceleration structure and absent horizontal terrain is a solid simulation boundary. General body and contact domains, persistent manifolds, sleeping, and constraint coloring remain the path described by #Y5H8KC.
- Let the world arrive. Done as a first durable proof: deterministic staged generation, sparse edit replay, bounded residency, eviction, recovery, and asynchronous versioned checkpoints now preserve building and resume state across standalone sessions. #TR2JNQ records the boundary and the possible event-log successor.
- See farther. Introduce a coarse domain only when there is enough world for level of detail to solve a visible problem.
- Let processes inhabit it. Fluids, growth, weathering, or ecology can produce narrow typed fields rather than swelling one universal cell record.
- Let something live in it. Done as a first small proof: a turtle wanders the resident surface on the player's own body protocol, is drawn as turned boxes on the block pipeline, and can be mounted and ridden through the modal focus the wall terminals already use. #KTRMAO and #W2E5HR record what an animal is and what riding one turned out to be.
- Give things weight. Done as a first pile: spheres in columnar bodies, coloured contacts, a soft-step solver with four-wide lanes, and a ball to throw, a fountain, and a lava spring in the world; the player and the animals push and are struck. #V090DQ and #JAU0EF record the engine and what the game does with it.
This is not a dependency graph. A particularly delightful experiment may pull a later item forward.
Mentioned in: The first animal is an entity, not a field, A seam, not a specification
Paths into the design #M9C4DJ
- Domains, bundles, and materialized worlds asks what the world owns and how its repeated values are stored.
- Voxel fields and chunk windows asks how block content, lighting, meshing, collision, and future processes can inhabit one chunked space without sharing ownership or rebuilding its traversal rules.
- Quantities, dimensions, and interpretation asks what numerical fields mean, which operations are lawful, and where units enter or leave.
- Physics data and SIMD execution asks how a CLOS engine can feed wide numerical kernels without turning bodies into tiny dispatching objects.
- The queue completion frontier remains relevant whenever derived GPU products outlive the Lisp frame that submitted them.
Questions to keep open #Q7V3LC
- Is the first streamed unit a cubic chunk, a vertical chunk column, or something taught by the procedural generator?
- Which block variation belongs in palette states and which deserves a field?
- When measurements justify changing the current byte-per-light-level representation, does nibble packing preserve a clean field boundary?
- When would an append-only edit log and periodic checkpoint repay their recovery, migration, and compaction machinery? #TR2JNQ
- Which coarse readings make a distant block world recognizable without pretending they are full-resolution blocks?
- Can a live inspector show a logical site, its source layers, resident representation, and derived products without creating a general property graph?
The interactive block world built on luv.
The brick panel swept from top left to bottom right by the mining film.
(&key
(title "luv little block world — click, look, walk")
;; NIL means "as much of this display as
;; comfortably fits"; a capture asks for the
;; exact frame it intends to write out. A
;; KMSDRM console can only present at a real
;; display mode, so the environment may pin
;; the canvas to the panel's native size.
(width (let ((value (uiop:getenv "LUVCRAFT_WIDTH")))
(and value (parse-integer value))))
(height (let ((value (uiop:getenv "LUVCRAFT_HEIGHT")))
(and value (parse-integer value))))
(frames-per-second 60)
(visible-p t)
(fullscreen-p nil)
(high-pixel-density-p t)
(world (make-empty-little-block-world))
(mesher (make-instance
'exposed-face-mesher))
(camera (make-instance 'fly-camera))
player
(selected-block *stone-block*)
(inventory (make-block-inventory))
quit-function
;; A hidden capture wanting animals in frame
;; hands in a population it has already
;; placed; the ordinary game grows its own
;; around the player as it plays.
(critters
(make-instance 'critter-population))
checkpoint-writer
(provider *gpu-provider*)
(sky-clock (make-instance 'sky-clock))
(sky-profile (make-default-sky-profile))
(shadow-diagnostic-p nil)
;; The world-text banner is a proof of the
;; Slug path, not scenery: a caller that
;; wants one asks for it, and the ordinary
;; game sky stays empty.
(world-text-string nil)
(world-text-font-pathname
(cl-dejavu:font-pathname "DejaVuSans.ttf"))
(world-text-distance 8.0)
(world-text-lift 3.0)
(world-text-units-per-em 0.55)
(video-pathname nil)
(video-distance 13.0)
(video-lift 4.5)
(video-height 5.0)
(residency-radius 6)
(publication-limit 2)
(load-schedule-limit 4)
(mesh-capture-limit 1))Open a little CPU-meshed block world. Click to capture the pointer, look with the mouse, walk with WASD, and jump with Space. Once captured, left click removes the block at the centre of view and right click places the selected block. Number keys select materials, middle click picks the targeted material, Shift…
(session)(session pathname &key camera-pose metadata-function
(include-hud-p t) (include-viewmodel-p t))Render SESSION once offscreen and write its native GPU frame to PNG. This works for both live and hidden sessions. The call returns only after GPU readback and compressed PNG writing have completed; it does not inspect the host window or depend on the window being visible. CAMERA-POSE may be a pose or a function of…
(session)Stop SESSION exactly once and publish its result to every caller. The sole owner attempts every named release step and closes the canvas and device last. Concurrent and later callers observe the same values or RELEASE-ERROR without releasing a native handle twice.
(world &key camera player selected-block carried)(world)Return WORLD as a portable description without resident materializations.
(camera player selected-block &optional carried)CARRIED are the blocks the player holds that are worth writing down: per-instance ones such as films, which no palette would give back.
Until now the player was a camera: an eye at a height above a collision box, with an inventory of materials and nothing to carry them in. The body in body.lisp gives the eye two arms. They hang into the bottom corners of the view, sleeve and wrist and hand, but are no longer little boxes. player-body owns a live…
The first thing to hold is a phone, because it is funny: a slab of near-black glass a little too large for the hand, held up in front of the face the way everyone holds theirs, and on it a live shell. It is not a new kind of screen. phone.lisp builds the very display TERMINAL-WALL.LISP builds for a wall (#8JCMA5)…
A turtle is the first thing in the little world with a life of its own, and it is built the way #F6R9QA says such a thing should be: a CLOS instance with a position, a heading, a small mind, and an independently inspectable lifetime, rather than a reading of a dense field over sites. There are a handful of them alive…
A cell can be addressed without being an individual in the engine's object model. It normally has no independent allocation, ownership, or lifecycle; its identity is the site's identity inside a domain. Its block content, light, moisture, or temperature are values borne at that site. This suggests three useful…
CLOS does not require one heap object per cell. It helps express which things really have identity, ownership, behaviour, and independently inspectable lifetimes. Likely objects include: – a world source, composition, or edit overlay; – a finite domain and a resident chunk over it; – a field specification and its…
The order below is meant to keep each architectural proof attached to a new experience: – Look around a little world. Done: the camera inhabits generated terrain. – Touch it. Done as a first correctness pass: ray traversal selects, removes, and places blocks, and revisions make the edit visible to the renderer. –…
Luvcraft owns one optional modal focus independently of its overlay list. A focused object receives canvas events while ordinary walking, looking, block editing, jumping, and material selection are suspended. Entering or leaving focus clears held player input and releases relative-pointer capture, so a movement key…
The existing block-kind instances behave like palette entries: many sites share one object that owns colour and generic behaviour. A chunk now stores compact unsigned 16-bit indices into a chunk-local vector of those semantic objects. world-block-at returns the shared object after decoding, while a whole-column…
The domain/bundle vocabulary was developed for terrain, but its real claim is broader: a finite domain identifies sites, and fields have values over those sites (#B8R3KF). Box3D, read in this vocabulary, is a physics engine built from exactly such domains — with the twist that domain membership is dynamic: – The…
Mounting a turtle is the same session transition as reading a wall terminal (#8JCMA5), and it is meant to feel like one: TAB enters the interaction with whatever the crosshair is on, shift-TAB leaves it, ordinary player input stands down for the duration, and the camera eases out of wherever it was into the pose the…
Intent. Build the slice #G7Q3XR proposed, no wider: spheres against the voxel lattice and each other, the Soft Step skeleton, persistent contacts, colouring from the first day, one scalar reference kernel and one four-wide kernel per instruction family, and enough game around it that the block world gains a ball to…
The engine knows spheres; the game knows kinds (*body-kinds* in luvcraft/balls.lisp): a ball, a marble, a water drop, a lava gobbet, each a tile of the atlas, a radius, a mass, a bounce, a friction, a life. Every body is drawn as a cube of its tile turned by its orientation, written by one closed loop into the block…
The little block world is the first luv experiment whose success is mostly felt rather than inferred. It opens a window, gives the player a place to look around, and makes changes in world representation, meshing, lighting, and physics visible immediately. That makes it an unusually good path through otherwise…
The current implementation lives in luvcraft/, split between the renderer-independent model in luvcraft/world.lisp and the visible slice above it (blocks, terrain, meshing, simulation, and the application files): – a voxel-space records a fixed chunk shape and explicit physical cell extent; – distinct world, chunk,…
A luvcraft save is a readable, versioned s-expression containing the block-world space, its procedural source, and the sparse authored edit overlay. Block values use durable semantic names such as :stone and :crystal rather than printed CLOS identities. Explicit air remains a value, distinct from the absence of an…
Two authored play sequences through the ordinary centre-ray edit verb. The wall and scaffold are merely deterministic terrain; every filmed removal and placement still crosses
luvcraft's player mutation, particle, lighting, mesh-publication, and checkpoint-notification path. #B4K7WR #P3L8YX