luv

Workshop wiki

app.lisp

luvcraft/app.lisp

system luvcraft/core · 158 definitions · on GitHub

The luvcraft application object and its direct interactions.

A luvcraft-session coordinates the canvas, GPU device, world, camera, player, and a luvcraft-renderer which solely owns the frame GPU artifacts. This file defines that coordinator plus the player-facing verbs: aiming along the view ray, selecting materials, and placing or removing blocks. Chunk streaming lives in streaming.lisp and frame rendering in render.lisp.

in-package#:luvcraft
luv.arithmetic:define-quantity-constant+luvcraft-target-reach+8d0:typedouble-float:quantity
:quantity:ray-distance:unit:cell

Every scene stage writes linear radiance into a floating-point attachment instead of an already-encoded display image. A sun disc, a specular glint, or an emissive crystal is then allowed to be far brighter than display white, which is exactly the signal the bloom chain feeds on and the tonemapper rolls off. Only the final presentation image is an sRGB eight-bit surface.

defconstant+luvcraft-scene-color-format+:rgba16-float"The linear HDR attachment format shared by every scene-stage pipeline."
defconstant+luvcraft-bloom-color-format+:rgba16-float"The reduced-resolution attachment format of the bloom and shaft chain."
defconstant+luvcraft-bloom-divisor+4"How much smaller each bloom chain attachment is than the frame."
defclassluvcraft-session
canvas:initarg:canvas:readerluvcraft-session-canvas
device:initarg:device:readerluvcraft-session-device
context:initarg:context:readerluvcraft-session-context
world:initarg:world:readerluvcraft-session-world
checkpoint-writer:initarg:checkpoint-writer:initformnil:readerluvcraft-session-checkpoint-writer
mesher:initarg:mesher:readerluvcraft-session-mesher
production-system:initarg:production-system:initformnil:readerluvcraft-session-production-system
desired-chunks:initform
make-hash-table:test#'equal
:readerluvcraft-session-desired-chunks
next-residency-demand:initform0:accessorluvcraft-session-next-residency-demand
outstanding-production:initform
make-hash-table:test#'equal
:readerluvcraft-session-outstanding-production
production-errors:initformnil:accessorluvcraft-session-production-errors
publication-limit:initarg:publication-limit:initform2:accessorluvcraft-session-publication-limit
load-schedule-limit:initarg:load-schedule-limit:initform4:accessorluvcraft-session-load-schedule-limit
mesh-capture-limit:initarg:mesh-capture-limit:initform1:accessorluvcraft-session-mesh-capture-limit
chunk-products:initform
make-hash-table:test#'equal
:readerluvcraft-session-chunk-products
staged-chunk-products:initform
make-hash-table:test#'equal
:readerluvcraft-session-staged-chunk-products
meshed-world-revision:initarg:meshed-world-revision:initform-1:accessorluvcraft-session-meshed-world-revision
camera:initarg:camera:readerluvcraft-session-camera
lighting-state:initarg:lighting-state:initformnil:readerluvcraft-session-lighting-state
sky-clock:initarg:sky-clock:initform
make-instance'sky-clock
:accessorluvcraft-session-sky-clock
sky-profile:initarg:sky-profile:initform:accessorluvcraft-session-sky-profile
shadow-diagnostic-p:initarg:shadow-diagnostic-p:initformnil:accessorluvcraft-session-shadow-diagnostic-p
shadow-anchor:initformnil:accessorluvcraft-session-shadow-anchor:documentation

The world point the shadow texel lattice pivots about; shadow-frame-rows walks it after the camera in whole texels.

player:initarg:player:initformnil:readerluvcraft-session-player
residency-radius:initarg:residency-radius:initform6:accessorluvcraft-session-residency-radius
residency-center:initformnil:accessorluvcraft-session-residency-center
selected-block:initarg:selected-block:initform*stone-block*:accessorluvcraft-session-selected-block
inventory:initarg:inventory:initform:readerluvcraft-session-inventory
particle-system:initarg:particle-system:initform
make-instance'block-particle-system
:readerluvcraft-session-particle-system
critters:initarg:critters:initform
make-instance'critter-population
:readerluvcraft-session-critters

The player's own arms and hands, and what they hold. See BODY.LISP.

body:initarg:body:initform
make-instance'player-body
:readerluvcraft-session-body

The things with weight: balls, drops, gobbets. See PHYSICS.LISP for the world and BALLS.LISP for what the game does with it. Made on the first frame, when there is a block world to collide with.

physics:initarg:physics:initformnil:accessorluvcraft-session-physics
physics-clock:initform0d0:typedouble-float:accessorluvcraft-session-physics-clock

The CPU-side vertex stream the bodies are drawn from each frame, and how much of it the last build filled.

physics-vertex-stream:initformnil:accessorluvcraft-session-physics-vertex-stream
physics-vertex-count:initform0:accessorluvcraft-session-physics-vertex-count

Where the world's springs stand, found from its authored edits.

springs:initformnil:accessorluvcraft-session-springs
title-base:initarg:title-base:initform"luvcraft":readerluvcraft-session-title-base
renderer:initarg:renderer:initform
make-instance'luvcraft-renderer
:readerluvcraft-session-renderer
world-text:initarg:world-text:initformnil:readerluvcraft-session-world-text
world-text-glyph-cache:initarg:world-text-glyph-cache:initformnil:readerluvcraft-session-world-text-glyph-cache
video-screen:initarg:video-screen:initformnil:accessorluvcraft-session-video-screen

When set, every presented frame is also copied into this texture, so something outside the window -- another process, through an IOSurface -- can watch the game.

frame-mirror:initformnil:accessorluvcraft-session-frame-mirror
overlays:initformnil:accessorluvcraft-session-overlays
lobby-client:initformnil:accessorluvcraft-session-lobby-client:documentation"The reconnecting tailnet radio owned by this session, or NIL."
modal-focus:initformnil:accessorluvcraft-session-modal-focus
focus-camera-origin:initformnil:accessorluvcraft-session-focus-camera-origin
focus-toggle-tab-down-p:initformnil:accessorluvcraft-session-focus-toggle-tab-down-p
movement-intent:initform:readerluvcraft-session-movement-intent:documentation"What the player is trying to do, set by the input layer."
look-intent:initform:readerluvcraft-session-look-intent:documentation

Where the player is trying to turn the camera, urged by the arrow keys: a mouse for the mouseless console.

pointer-captured-p:initformnil:accessorluvcraft-session-pointer-captured-p
pointer-x:initformnil:accessorluvcraft-session-pointer-x
pointer-y:initformnil:accessorluvcraft-session-pointer-y
pointer-dirty-p:initformt:accessorluvcraft-session-pointer-dirty-p
software-cursor-p:initarg:software-cursor-p:initformnil:readerluvcraft-session-software-cursor-p
quit-function:initarg:quit-function:initformnil:readerluvcraft-session-quit-function:documentation"An optional PLAY-level teardown run by the stop owner."
stop-controller:initarg:stop-controller:initform
make-stop-controller:name"luvcraft session"
:readerluvcraft-session-stop-controller:documentation"The separate one-shot owner and result publication for session teardown."
pointer-capture-suspended-p:initformnil:accessorluvcraft-session-pointer-capture-suspended-p:documentation

Whether mouse look was captured when the current modal focus was entered.

A modal interaction wants an ordinary cursor -- a terminal's own UI is clicked at a place on the screen, which relative pointer mode does not have -- so entering focus releases the capture. Remembering that it was held is what lets leaving focus put the player straight back into mouse look instead of making them click the world again.

last-frame-time:initformnil:type
ornulldouble-float
:quantity
:quantity:monotonic-frame-time:unit:second
:accessorluvcraft-session-last-frame-time
physics-accumulator:initform0d0:typedouble-float:quantity
:quantity:physics-accumulated-duration:unit:second
:accessorluvcraft-session-physics-accumulator
running-p:initformt:accessorluvcraft-session-running-p

Keep the established session-facing renderer vocabulary while making its storage and lifecycle belong to one semantic owner. These are methods, rather than captured closures, so a live class redefinition replaces the former slot-accessor methods at their ordinary CLOS coordinates.

defmacrodefine-luvcraft-renderer-forwarder
session-accessorrenderer-accessor&keysetf
`
progn
defmethod,session-accessor
,renderer-accessor
luvcraft-session-renderersession
,@
whensetf`
defmethod
setf,session-accessor
value
setf
,renderer-accessor
luvcraft-session-renderersession
value
defparameter+retired-session-renderer-slot-initargs+'
atlas-texture.:atlas-texture
atlas-view.:atlas-view
atlas-sampler.:atlas-sampler
normal-atlas-texture.:normal-atlas-texture
normal-atlas-view.:normal-atlas-view
shadow-depth-texture.:shadow-depth-texture
shadow-depth-view.:shadow-depth-view
shadow-depth-sampler.:shadow-depth-sampler
shadow-comparison-sampler.:shadow-comparison-sampler
layout.:layout
shadow-layout.:shadow-layout
post-layout.:post-layout
bloom-layout.:bloom-layout
linear-sampler.:linear-sampler
bloom-bright-pipeline.:bloom-bright-pipeline
bloom-horizontal-pipeline.:bloom-horizontal-pipeline
bloom-vertical-pipeline.:bloom-vertical-pipeline
sun-shaft-pipeline.:sun-shaft-pipeline
block-pipeline.:block-pipeline
shadow-pipeline.:shadow-pipeline
sky-vertex-buffer.:sky-vertex-buffer
sky-pipeline.:sky-pipeline
crosshair-vertex-buffer.:crosshair-vertex-buffer
cursor-vertex-buffer.:cursor-vertex-buffer
crosshair-pipeline.:crosshair-pipeline
cursor-pipeline.:cursor-pipeline
post-pipeline.:post-pipeline
frame-states.:frame-states
resources.:resources
"The former session slots transferred into a renderer by live migration."
defparameter+retired-session-frame-attachment-slots+'
render-extent.:render-extent
color-texture.:color-texture
color-view.:color-view
depth-texture.:depth-texture
depth-view.:depth-view
presentation-texture.:presentation-texture
presentation-view.:presentation-view
bloom-primary-texture.:bloom-primary-texture
bloom-primary-view.:bloom-primary-view
bloom-secondary-texture.:bloom-secondary-texture
bloom-secondary-view.:bloom-secondary-view
"The former session slots published together as one attachment cohort."
defunmake-luvcraft-renderer-from-retired-session-slots
sessionretired-values

Adopt a pre-renderer session's discarded slot values into one owner.

retired-values is the property list supplied by update-instance-for-redefined-class. Unbound old slots remain unbound in the new renderer; owned resource and frame-state collections retain identity.

let
missing
gensym"MISSING"
initargsnil
attachmentsnil
dolist
let
value
getfretired-values
carmapping
missing
unless
eqvaluemissing
setfinitargs
list*
cdrmapping
valueinitargs
dolist
let
value
getfretired-values
carmapping
missing
unless
eqvaluemissing
setfattachments
list*
cdrmapping
valueattachments
whenattachments
setfinitargs
list*:frame-attachmentsattachmentsinitargs
dolist
mapping'
device.:device
context.:context
when
slot-boundpsession
carmapping
setfinitargs
list*
cdrmapping
slot-valuesession
carmapping
initargs
apply#'make-instance'luvcraft-rendererinitargs
defmethodupdate-instance-for-redefined-class:after
added-slotsdiscarded-slotsretired-values&restinitargs
declare
ignoreinitargs
when
and
member'rendereradded-slots
intersectiondiscarded-slots

A running game is valuable state. Moving the ownership boundary must not require abandoning it or leave its old GPU objects unreachable. See #V9VH79.

setf
slot-valuesession'renderer
defgenericencode-luvcraft-overlay
overlaysessionpasssurface-texture
:documentation

Encode overlay into session's open scene pass for surface-texture.

defmethodencode-luvcraft-overlay
overlaysessionpasssurface-texture
declare
ignoreoverlaysessionpasssurface-texture
nil
defgenericluvcraft-overlay-stage
overlay
:documentation

Return where overlay draws: :scene, :WORLD-PANEL, :VIEWMODEL, :HUD, or :NONE.

Scene overlays inhabit world depth; viewmodels are first-person geometry above the world but below held items and the crosshair. World panels retain world projection and depth while drawing analytic application graphics at native presentation density. HUD overlays follow scene postprocessing. :NONE participates in no render pass.

defmethodluvcraft-overlay-stage
overlay
declare
ignoreoverlay
:scene
defgenericluvcraft-world-panel-depth
overlaysession
:documentation

Return overlay's positive camera-space depth for world-panel ordering.

Native-density panels are alpha-composited far to near before their completed depth layer meets the game scene. Non-spatial panel implementations may keep the stable attachment order by returning the default zero depth.

defmethodluvcraft-world-panel-depth
overlaysession
declare
ignoreoverlaysession
0.0
defgenericluvcraft-overlay-live-shader-pipelines
overlay
:documentation

The live shader pipelines overlay owns, so the session can count them among its own; none by default.

:method
overlay
declare
ignoreoverlay
nil
defgenericrefresh-luvcraft-overlay
overlaysession
:documentation

Publish any complete pending render state for overlay at a frame boundary.

defmethodrefresh-luvcraft-overlay
overlaysession
declare
ignoreoverlaysession
nil
defgenericadvance-luvcraft-overlay
overlaysessionseconds
:documentation

Advance overlay's simulation state by seconds at the frame boundary.

defmethodadvance-luvcraft-overlay
overlaysessionseconds
declare
ignoreoverlaysessionseconds
nil
defgenericrelease-luvcraft-overlay
overlay
:documentation

Release resources owned by an object attached to luvcraft.

defmethodrelease-luvcraft-overlay
overlay
declare
ignoreoverlay
nil
defgenericevict-luvcraft-overlay-frame-key
overlayframe-key
:documentation

Evict overlay resources retained for one canvas frame-key.

Offscreen captures use a fresh target, so an overlay which caches per-frame GPU state must detach that entry before the shared capture target is destroyed.

defmethodevict-luvcraft-overlay-frame-key
overlayframe-key
declare
ignoreframe-key
overlay
defgenerichandle-luvcraft-overlay-event
overlaysessioncanvasevent
:documentation

Handle event projected onto overlay, returning true when consumed.

defmethodhandle-luvcraft-overlay-event
overlaysessioncanvasevent
declare
ignoreoverlaysessioncanvasevent
nil
defgenericluvcraft-focus-entered
focussession
:documentation

Notify focus that session has entered its interaction mode.

defmethodluvcraft-focus-entered
focussession
declare
ignorefocussession
nil
defgenericluvcraft-focus-left
focussession
:documentation

Notify focus that session has left its interaction mode.

defmethodluvcraft-focus-left
focussession
declare
ignorefocussession
nil
defgenerichandle-luvcraft-focus-event
focussessioncanvasevent
:documentation

Handle event while focus owns session's modal player interaction.

defgenerichandle-luvcraft-focus-control-event
focussessioncanvasevent
:documentation

Offer event to controls which belong to focused focus, returning true when consumed.

This is narrower than the ordinary overlay event path: a focused world object may expose a HUD control without allowing an unconsumed click to fall through to block editing or to unrelated overlays.

defmethodhandle-luvcraft-focus-control-event
focussessioncanvasevent
declare
ignorefocussessioncanvasevent
nil
defgenericluvcraft-focus-score
focussession
:documentation

Return a non-negative targeting score when focus can be entered by TAB.

defmethodluvcraft-focus-score
focussession
declare
ignorefocussession
nil
defgenericluvcraft-focus-camera-pose
focussession
:documentation

Return focus's desired CAMERA-POSE in session, or NIL for an FOV-only cue.

defmethodluvcraft-focus-camera-pose
focussession
declare
ignorefocussession
nil
defgenericluvcraft-focus-carries-player-p
focus
:documentation

Whether focus moves the player itself instead of the player controller.

A terminal on a wall leaves the player standing in front of it and the ordinary scalar controller keeps running. A mount does not: it carries the player, so the controller must stand down for as long as the interaction lasts.

defmethodluvcraft-focus-carries-player-p
focus
declare
ignorefocus
nil
defgenericadvance-luvcraft-focus
focussessionseconds
:documentation

Let focus take its own turn in session's simulation, before the player's.

A focus which only reads input needs nothing here; a moving one -- a mount, a vehicle, a cutscene -- does its own work in this method.

defmethodadvance-luvcraft-focus
focussessionseconds
declare
ignorefocussessionseconds
nil
defgenericluvcraft-overlay-focus-insets
overlaysession
:documentation

Return left, top, right, and bottom pixel insets obscured by overlay.

defmethodluvcraft-overlay-focus-insets
overlaysession
declare
ignoreoverlaysession
values0.00.00.00.0
defunluvcraft-session-focus-insets
session
let
left0.0
top0.0
right0.0
bottom0.0
dolist
overlay
luvcraft-session-overlayssession
multiple-value-bind
overlay-leftoverlay-topoverlay-rightoverlay-bottom
setfleft
maxleftoverlay-left
top
maxtopoverlay-top
right
maxrightoverlay-right
bottom
maxbottomoverlay-bottom
valueslefttoprightbottom
defunluvcraft-session-focus-camera-active-p
session
not
null
luvcraft-session-focus-camera-originsession
defunluvcraft-session-return-camera-pose
session
let*
origin
luvcraft-session-focus-camera-originsession
player
and
slot-boundpsession'player
luvcraft-session-playersession
whenorigin
make-camera-pose
ifplayer
copy-camera-position
camera-pose-positionorigin
camera-pose-yaworigin
camera-pose-pitchorigin
camera-pose-field-of-vieworigin
defunadvance-luvcraft-focus-camera
sessionseconds

Advance session's cinematic focus pose after ordinary player simulation.

let
origin
luvcraft-session-focus-camera-originsession
when
andorigin
slot-boundpsession'camera
let*
camera
luvcraft-session-camerasession
focus
luvcraft-session-modal-focussession
error
advance-camera-focuscameratargetseconds
when
and
nullfocus
<error1e-4
set-camera-posecameratarget
setf
luvcraft-session-focus-camera-originsession
nil
session
defgenericactivate-luvcraft-target
blocksessionhit
:documentation

Create and return a focusable interaction for targeted block, or NIL.

defmethodactivate-luvcraft-target
blocksessionhit
declare
ignoreblocksessionhit
nil
defgenericactivate-luvcraft-critter
crittersession
:documentation

Create and return a focusable interaction for targeted critter, or NIL.

The animal counterpart of activate-luvcraft-target: an animal which can be mounted answers with the ride, and one which cannot answers NIL and is simply looked at.

defmethodactivate-luvcraft-critter
crittersession
declare
ignorecrittersession
nil
defgenericattach-luvcraft-hud
session
:documentation

Attach the player HUD supplied by the loaded presentation system.

LUVCRAFT/CORE deliberately has no presentation-system dependency. LUVCRAFT/MCCLIM specializes this at the session boundary and returns its attached overlay.

defmethodattach-luvcraft-hud
sessiont
declare
ignoresession
nil
defgenericstart-luvcraft-lobby
session
:documentation

Start session's optional background lobby service and return it.

defmethodstart-luvcraft-lobby
sessiont
declare
ignoresession
nil
defgenericstop-luvcraft-lobby
session
:documentation

Cooperatively stop session's lobby service, if loaded.

defmethodstop-luvcraft-lobby
sessiont
declare
ignoresession
nil
defgenericperform-luvcraft-stop
session
:documentation

Release session after its stop controller has granted sole ownership.

defunrequest-luvcraft-quit
session

Request session's one orderly teardown beside its native canvas thread.

Native close and commands may call this without waiting. Exactly one worker runs the PLAY-level quit callback when present, or the session teardown itself otherwise; repeated requests still defer native close to that same owner.

Close capture admission on the native caller before its teardown worker exists. A capture which won the race remains safe to finish; no later request can enter behind the owner's eventual frame-boundary barrier.

setf
luvcraft-session-running-psession
nil
request-controlled-stop
luvcraft-session-stop-controllersession
lambda
let
quit-function
luvcraft-session-quit-functionsession
ifquit-function
funcallquit-functionsession
:thread-name"luvcraft session stop"
t
defgenericluvcraft-key-hint
thing
:documentation

Return a short string naming the keystroke that reaches thing, or NIL.

A control which draws its own key hint would be inventing one: the keys are decided in the command layer above, so the drawing asks rather than guesses, and a rebound key changes the label on the button.

:method
thing
declare
ignorething
nil
defunclear-luvcraft-player-input
session
clear-movement-intent
luvcraft-session-movement-intentsession
clear-movement-intent
luvcraft-session-look-intentsession
when
luvcraft-session-pointer-captured-psession
set-canvas-relative-pointer-mode
luvcraft-session-canvassession
nil
setf
luvcraft-session-pointer-captured-psession
nil
session
defunresume-luvcraft-pointer-capture
session

Take mouse look back if the modal interaction just left had suspended it.

when
shiftf
luvcraft-session-pointer-capture-suspended-psession
nil
when
and
luvcraft-session-running-psession
not
luvcraft-session-pointer-captured-psession

Mouse input and the cinematic return cannot both own the camera. Finish the return before publishing capture, otherwise every mouse delta is followed by advance-luvcraft-focus-camera pulling yaw and pitch back toward the pose saved on entry.

alexandria:when-let
set-camera-pose
luvcraft-session-camerasession
pose
setf
luvcraft-session-focus-camera-originsession
nil
set-canvas-relative-pointer-mode
luvcraft-session-canvassession
t
setf
luvcraft-session-pointer-captured-psession
t
session
defununfocus-luvcraft-session
session

Leave session's modal interaction, returning the object which was focused.

let
focus
luvcraft-session-modal-focussession
whenfocus

Publish the unfocused state before the callback so a callback may safely establish another focus without being cleared afterward.

setf
luvcraft-session-modal-focussession
nil
focus
defunfocus-luvcraft-session
sessionfocus

Enter modal interaction with focus, suspending ordinary player input.

This is the common session transition behind using a world terminal or book, mounting a vehicle, and other interactions described by #8JCMA5.

check-typesessionluvcraft-session
when
nullfocus
error"Use UNFOCUS-LUVCRAFT-SESSION to leave modal focus."
unless
eqfocus
luvcraft-session-modal-focussession

Return the previous interaction all the way to the player's pose first. A direct focus-to-focus transition then borrows that same pose anew; recording before UNFOCUS would let its resume path consume the origin and leave the second interaction with nothing to return to.

when
and
slot-boundpsession'camera
null
luvcraft-session-focus-camera-originsession
setf
luvcraft-session-focus-camera-originsession
camera-pose-from-camera
luvcraft-session-camerasession

Read the capture back after the previous focus has returned it, so a focus-to-focus move keeps mouse look owed to the player rather than forgetting it halfway.

let
captured
luvcraft-session-pointer-captured-psession
setf
luvcraft-session-pointer-capture-suspended-psession
captured
setf
luvcraft-session-modal-focussession
focus
handler-case
error
condition
setf
luvcraft-session-modal-focussession
nil
errorcondition
focus
defmacroguarding-luvcraft-overlay
sessionoverlayphase
&bodybody

Run body for OVERLAY; an error is OVERLAY's alone. It is retained on the session's canvas with its backtrace, logged, and the overlay is taken out of the session -- a paint bug in a gadget must not take the world with it. Returns body's values, or NIL when the overlay failed.

let
backtrace
gensym"BACKTRACE"
`
let
,backtracenil
handler-case
handler-bind
error
lambda
condition
declare
ignorecondition
,@body
error
condition
fuse-luvcraft-overlay,session,overlay,phasecondition,backtrace
nil
defunfuse-luvcraft-overlay
sessionoverlayphaseconditionbacktrace

overlay failed in phase with condition: retain the failure and drop it. The overlay's resources are not released here -- this may be mid-frame, with its buffers still in the command stream -- so they are leaked, which a development image can afford and a frame cannot.

luv:retain-canvas-failure
luvcraft-session-canvassession
phaseconditionbacktrace
luv:log-event:luvcraft"overlay ~A is fused after failing in ~(~A~)"
type-ofoverlay
phase
remove-luvcraft-overlaysessionoverlay:release-pnil
overlay
zdefun
dispatch-luvcraft-focus-event:zone:luvcraft/focus-event
sessioncanvasevent
let
focus
luvcraft-session-modal-focussession
whenfocus
if
memberfocus
luvcraft-session-overlayssession
guarding-luvcraft-overlay
sessionfocus:focus-event
t
defunluvcraft-session-targeted-critter
session&key

Return the animal session's centre view ray reaches first, and how far.

An animal standing behind a wall is not targeted: whichever of the animal and the terrain the ray meets first is what the player is looking at.

let
camera
luvcraft-session-camerasession
multiple-value-bind
rightupforward
declare
ignorerightup
multiple-value-bind
critter-along-ray
luvcraft-session-critterssession
camera-positioncamera
forwardmax-distance
whencritter
let
hit
luvcraft-session-targetsession:max-distancemax-distance
unless
andhit
<
block-ray-hit-distancehit
distance
defunluvcraft-session-focus-candidate
session

Return or activate session's best currently targeted focusable object.

or
loopwithbest=nilwithbest-score=nilforoverlayin
luvcraft-session-overlayssession
forscore=
luvcraft-focus-scoreoverlaysession
when
andscore
or
nullbest-score
<scorebest-score
do
setfbestoverlaybest-scorescore
finally
returnbest
multiple-value-bind
hitstatus
declare
ignorestatus
whenhit
activate-luvcraft-target
block-ray-hit-blockhit
sessionhit
defuntoggle-luvcraft-session-focus
session

Leave modal focus, or enter the best overlay currently targeted by session.

if
luvcraft-session-modal-focussession
alexandria:when-let
focus-luvcraft-sessionsessioncandidate
defundispatch-luvcraft-overlay-event
sessioncanvasevent

Offer event to session's frontmost overlay and report consumption.

some
lambda
overlay
guarding-luvcraft-overlay
sessionoverlay:overlay-event
luvcraft-session-overlayssession
defunluvcraft-overlay-mutation-canvas
session

Return session's live canvas, or NIL while its native owner does not exist.

when
slot-boundpsession'canvas
luvcraft-session-canvassession
defuncall-with-luvcraft-overlay-mutation
sessionfunction

Run function at session's native frame boundary when one exists.

Startup and post-quiescence cleanup still execute directly. Once the canvas is open, request-canvas-frame makes attachment mutation and GPU release part of the native owner stream, after any frame which borrowed the overlay list, and synchronously returns function's values or condition to the caller.

defun%add-luvcraft-overlay
sessionoverlay
let
installed-pnil
unwind-protect-releasing
progn
call-with-running-stop-controller
luvcraft-session-stop-controllersession
lambda

Recheck at publication: two callers may both have reached this boundary before either became visible.

pushnewoverlay
luvcraft-session-overlayssession
:test#'eq
overlay
:attachmentoverlay:already-attached-p
lambda
memberoverlay
luvcraft-session-overlayssession
:test#'eq
setfinstalled-pt
overlay

ADD consumes a newly offered attachment on either outcome. A rejected constructor therefore unwinds with no frame, pipeline, worker, or other application resource stranded beside the game.

unlessinstalled-p
releasing:rejected-overlay
defunadd-luvcraft-overlay
sessionoverlay

Attach overlay at session's next native frame boundary and return it.

ADD consumes a newly offered overlay on both success and terminal rejection. Once session begins stopping, the rejected overlay is released exactly once before application-attachment-closed is signalled.

An EQ object already owned by this session is not a fresh offer. Resolve that idempotent case before a closing canvas can reject the owner request and make the caller mistake registered state for unpublished state.

when
memberoverlay
luvcraft-session-overlayssession
:test#'eq
return-fromadd-luvcraft-overlayoverlay
let
consumed-pnil
unwind-protect-releasing
progn
handler-case
call-with-luvcraft-overlay-mutationsession
lambda
setfconsumed-pt
%add-luvcraft-overlaysessionoverlay
application-attachment-closed
condition
errorcondition
error
condition

A native request can lose its closing-canvas race before the mutation starts. Once the application gate is terminal, name that semantic rejection instead of leaking the backend detail through ADD's ownership boundary.

let*
controller
luvcraft-session-stop-controllersession
state
cond

Another serialized add may have published this same EQ object before our native request lost its close race.

and
notconsumed-p
memberoverlay
luvcraft-session-overlayssession
:test#'eq
setfconsumed-pt
overlay
and
notconsumed-p
not
eq:runningstate
error'application-attachment-closed:controllercontroller:attachmentoverlay:statestate
t
errorcondition
overlay

request-canvas-frame can itself reject a closing canvas before the mutation callback begins. The registry has not consumed overlay in that case, so no frame can have borrowed it. Finish the transfer on this caller: GPU destroy is synchronized by the backend retirement ledger and is therefore safe even while terminal device teardown wins the race beside us.

unlessconsumed-p
releasing:unpublished-overlay
defun%remove-luvcraft-overlay
sessionoverlayrelease-p
setf
luvcraft-session-overlayssession
deleteoverlay
luvcraft-session-overlayssession
:test#'eq
when
eqoverlay
luvcraft-session-modal-focussession
whenrelease-p
overlay
defunremove-luvcraft-overlay
sessionoverlay&key
release-pt

Detach overlay at a frame boundary and optionally release it there.

The no-release path is intentionally preserved for fuse-luvcraft-overlay: that path may run inside a frame whose command stream still borrows overlay.

defunrequest-luvcraft-session-checkpoint
session

Capture session's durable state and submit it to its asynchronous writer.

let
writer
luvcraft-session-checkpoint-writersession
whenwriter
request-world-checkpointwriter
make-luvcraft-save-description
luvcraft-session-worldsession
:camera
luvcraft-session-camerasession
:player
luvcraft-session-playersession
:selected-block
luvcraft-session-selected-blocksession
:carried
block-inventory-carried-blocks
luvcraft-session-inventorysession
defunluvcraft-session-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-shadow-native-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-crosshair-native-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-cursor-native-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-sky-native-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-post-native-pipeline
session
live-shader-pipeline-native-pipeline
defunluvcraft-session-live-shader-pipelines
session

Every live shader pipeline coordinated by session, optional ones included.

removenil
append
luvcraft-renderer-pipelines
luvcraft-session-renderersession
list
and
luvcraft-session-world-textsession
world-text-run-pipeline
luvcraft-session-world-textsession
and
luvcraft-session-video-screensession
video-screen-pipeline
luvcraft-session-video-screensession
loopforoverlayin
luvcraft-session-overlayssession
append
defmethodapplication-live-artifacts

Enumerate Luvcraft's per-pipeline artifacts for shared developer tools.

defunrefresh-luvcraft-shaders
session

Install any successfully redefined block-world shader methods.

defunluvcraft-session-target
session&key

Raycast from session's camera through resident block terrain.

let
camera
luvcraft-session-camerasession
multiple-value-bind
rightupforward
declare
ignorerightup
raycast-block-world
luvcraft-session-worldsession
camera-positioncamera
forward#'block-solid-p:max-distancemax-distance
defunupdate-luvcraft-session-title
session
let*
blocks
block-inventory-quickbar-blocks
luvcraft-session-inventorysession
block
luvcraft-session-selected-blocksession
number
positionblockblocks:test#'eq
item
player-body-hand-item
luvcraft-session-bodysession
when
slot-boundpsession'canvas
setf
canvas-title
luvcraft-session-canvassession
formatnil"~A — [~A] ~(~A~)~@[ · holding ~A~] · ~A select · e place · x mine · c pick · I inventory · F phone · shift sprint · arrows look · tab focus · ctrl-q quit"
luvcraft-session-title-basesession

The tenth slot is the 0 key, so its chip says 0 rather than advertising a 10 key that does not exist.

ifnumber
mod
1+number
10
"inventory"
block-kind-nameblock
if
=10
lengthblocks
"1–9,0"
formatnil"1–~D"
lengthblocks
session
defunselect-luvcraft-block
sessionnumber

Select the one-based numbered placeable material and update the title.

check-typenumber
integer1
let
block
nth
1-number
block-inventory-blocks
luvcraft-session-inventorysession
whenblock
setf
luvcraft-session-selected-blocksession
block
block
defunrefresh-luvcraft-inventory
session

Give session any newly defined placeable materials, in number-key order.

The live half of adding a material: refresh-block-atlas repaints the wall, and this puts the new block on the number row of a running session. Existing entries keep their identity and quantities; entries the palette no longer names stay reachable at the end, past the number keys.

let*
inventory
luvcraft-session-inventorysession
entries
block-inventory-entriesinventory
setf
block-inventory-entriesinventory
append
loopforblockinpalettecollect
or
findblockentries:key#'block-inventory-entry-block
make-instance'block-inventory-entry:blockblock:quantitynil
remove-if
lambda
entry
member
block-inventory-entry-blockentry
palette
entries
session
defparameter+luvcraft-keyboard-look-rate+2.2d0"Radians per second of camera turn while an arrow key is held."
defunadvance-luvcraft-keyboard-look
sessionseconds

Turn session's camera as its look intent urges: a mouse for the mouseless.

let*
intent
luvcraft-session-look-intentsession
camera
luvcraft-session-camerasession
yaw-amount
movement-intent-axisintent:right:left
pitch-amount
movement-intent-axisintent:up:down
unless
zeropyaw-amount
incf
unless
zeroppitch-amount
setf
session
defunpick-luvcraft-block
session

Select the material currently under the centre crosshair.

multiple-value-bind
hitstatus
whenhit
setf
luvcraft-session-selected-blocksession
block-ray-hit-blockhit
values
andhit
block-ray-hit-blockhit
status
defgenericluvcraft-block-placed
blocksessionxyz
:documentation

block has just been put down at X,Y,Z by the player in session.

Most blocks are inert and the default does nothing; a block that does something where it stands -- a film beside a wall -- answers here.

:method
blocksessionxyz
declare
ignoreblocksessionxyz
nil
defgenericluvcraft-block-removed
blocksessionxyz
:documentation

block has just been taken away from X,Y,Z by the player in session.

:method
blocksessionxyz
declare
ignoreblocksessionxyz
nil
defunedit-luvcraft-block
sessionaction

Apply action (:REMOVE or :PLACE) along session's centre view ray.

multiple-value-bind
hitstatus
unlesshit
return-fromedit-luvcraft-block
valuesnilstatus
let*
world
luvcraft-session-worldsession
coordinate
ecaseaction
:remove
block-ray-hit-coordinatehit
:place
block-ray-hit-adjacent-coordinatehit
unlesscoordinate
return-fromedit-luvcraft-block
valuesnil:blocked
let
x
world-coordinate-xcoordinate
y
world-coordinate-ycoordinate
z
world-coordinate-zcoordinate
multiple-value-bind
old-blockresidency
unless
eqresidency:resident
return-fromedit-luvcraft-block
valuesnil:absent

The ground is about to move: whatever was asleep on it must find out.

when
luvcraft-session-physicssession
wake-physics-bodies-near
luvcraft-session-physicssession
+x0.5
+y0.5
+z0.5
2.5
ecaseaction
:remove
edit-block-atnilworldxyz
smash-block-particles
luvcraft-session-particle-systemsession
old-blockcoordinate

A block worth carrying is picked up rather than smashed away.

when
add-block-to-inventory
luvcraft-session-inventorysession
old-block1
setf
luvcraft-session-selected-blocksession
old-block
luvcraft-block-removedold-blocksessionxyz
:place
whenold-block
return-fromedit-luvcraft-block
valuesnil:blocked
when
player-overlaps-block-p
luvcraft-session-playersession
xyz
return-fromedit-luvcraft-block
valuesnil:blocked
let
block
luvcraft-session-selected-blocksession

Creative materials are unlimited; a carried one is spent.

when
and
not
remove-block-from-inventory
luvcraft-session-inventorysession
block1
return-fromedit-luvcraft-block
valuesnil:blocked
edit-block-atblockworldxyz
luvcraft-block-placedblocksessionxyz
valuescoordinate:edited

--------------------------------------------------------------------- The session's knobs: values that live on the session, its camera, its player, its sky clock, its animals. All are read every frame.

defun
degreescamera
setf
coerce
*degrees
/pi180
'single-float
degrees
defuncamera-sensitivity-milliradians
camera
*1000
camera-sensitivitycamera
defun
milliradianscamera
setf
camera-sensitivitycamera
coerce
/milliradians1000
'single-float
milliradians
define-knobtime-of-day
:group:sky:quantity
:quantity:time-of-day:unit:hour
:minimum0.0:maximum24.0:step0.25
sky-clock-hour
luvcraft-session-sky-clocksession
define-knobday-length
:group:sky:quantity
:quantity:day-length:unit:minute
:minimum0.5:maximum60.0:step0.5
sky-clock-minutes-per-day
luvcraft-session-sky-clocksession
define-knobfreeze-time
:group:sky:class'switch-knob:label"freeze the clock":quantity
:quantity:switch:unit:one
sky-clock-paused-p
luvcraft-session-sky-clocksession
define-knobfield-of-view
:group:camera:quantity
:quantity:camera-field-of-view:unit:degree
:typedouble-float:minimum30.0:maximum140.0:step1.0
camera-field-of-view-degrees
luvcraft-session-camerasession
define-knoblook-sensitivity
:group:camera:label"mouse sensitivity":quantity
:quantity:look-sensitivity:unit:milliradian
:unit-label" mrad/px":minimum0.2:maximum20.0:step0.1
camera-sensitivity-milliradians
luvcraft-session-camerasession
define-knobshadow-diagnostic
:group:shadows:class'switch-knob:label"shadow diagnostic view":quantity
:quantity:switch:unit:one
luvcraft-session-shadow-diagnostic-psession
define-knobwalk-speed
:group:player:quantity
:quantity:player-walk-speed:unit
:cell1
:second-1
:typedouble-float:minimum0.5:maximum30.0:step0.5
player-walk-speed
luvcraft-session-playersession
define-knobjump-speed
:group:player:quantity
:quantity:player-jump-speed:unit
:cell1
:second-1
:typedouble-float:minimum0.0:maximum30.0:step0.5
player-jump-speed
luvcraft-session-playersession
define-knobgravity
:group:player:quantity
:quantity:gravity-magnitude:unit
:cell1
:second-2
:typedouble-float:minimum0.0:maximum100.0:step1.0
player-gravity
luvcraft-session-playersession
define-knobeye-height
:group:player:quantity
:quantity:player-eye-height:unit:cell
:typedouble-float:minimum0.2:maximum3.0:step0.05
player-eye-height
luvcraft-session-playersession
define-knobcritter-count
:group:critters:label"animals about":quantity
:quantity:critter-count:unit:one
:minimum0:maximum12:step1
critter-population-target-count
luvcraft-session-critterssession