luv

Workshop wiki

game.lisp

luft/render/game.lisp

system luft/render · 51 definitions · on GitHub

in-package#:luft.render

The small semantic game layer over luft's packed terrain.

A player is an inspectable object at the application boundary. Terrain queries stay direct packed-chain reads, and rendering consumes two dense vec4 lanes made from this state once per frame.

defconstant+walking-player-gravity+-24.0
defparameter*walking-path-horizontal-radius*80"Farthest horizontal cell distance admitted by one click-to-walk route."
defparameter*walking-path-vertical-radius*16"Farthest vertical cell distance admitted by one click-to-walk route."
defparameter*walking-path-visit-limit*20000"Maximum standable cells considered by one click-to-walk search."
defparameter*walking-route-arrival-radius*0.09"Horizontal distance from a cell centre which completes one waypoint."
defconstant+thrown-ball-speed+18.0
defconstant+thrown-ball-radius+0.32
defgenericinspection-source-solid
source
:documentation

Return source's packed solid chain for sparse queries.

defmethodinspection-source-solid
sourcet
source
defmethodinspection-source-solid
sourcescene
scene-solidsource
defuncollision-cell-occupancy-bit
solidxyz

Read solid for gameplay, treating its finite horizontal boundary as wall.

Meshing and other world clients retain explicit outside-domain semantics. Only physical occupants choose this restart, so walkers slide along the box and projectile terrain publication materializes nearby boundary colliders.

handler-bind
luft:outside-domain
lambda
condition
declare
ignorecondition
invoke-restart'luft:treat-as-solid
defclasswalking-player
position:initarg:position:accessorwalking-player-position
previous-position:initarg:position:accessorwalking-player-previous-position
heading-x:initarg:heading-x:initform0.0:accessorwalking-player-heading-x
heading-y:initarg:heading-y:initform1.0:accessorwalking-player-heading-y
previous-heading-x:initarg:heading-x:initform0.0:accessorwalking-player-previous-heading-x
previous-heading-y:initarg:heading-y:initform1.0:accessorwalking-player-previous-heading-y
gait:initform0.0:accessorwalking-player-gait
previous-gait:initform0.0:accessorwalking-player-previous-gait
speed:initarg:speed:initform+walking-player-speed+:accessorwalking-player-speed
vertical-velocity:initform0.0:accessorwalking-player-vertical-velocity
grounded-p:initformt:accessorwalking-player-grounded-p
route:initformnil:accessorwalking-player-route
jump-requested-p:initformnil:accessorwalking-player-jump-requested-p
spell-flash:initform0.0:accessorwalking-player-spell-flash
ball-position:initformnil:accessorwalking-player-ball-position
previous-ball-position:initformnil:accessorwalking-player-previous-ball-position
physics:initform:readerwalking-player-physics
ball-handle:initformnil:accessorwalking-player-ball-handle
physics-clock:initform0.0d0:accessorwalking-player-physics-clock
:documentation

The continuous, player-owned state of luft's walking character.

position is the centre of the character's feet. Heading and gait are semantic animation inputs; keys and shader clocks are deliberately absent.

defclasswalking-route
start:initarg:start:readerwalking-route-start
destination:initarg:destination:readerwalking-route-destination
cells:initarg:cells:accessorwalking-route-cells
status:initarg:status:initform:running:accessorwalking-route-status
detail:initarg:detail:initformnil:accessorwalking-route-detail
visits:initarg:visits:initform0:readerwalking-route-visits
:documentation

One inspectable discrete intention realized by the continuous player.

START, DESTINATION, and CELLS are packed luft cell sites at the character's foot height. The route owns no duplicate terrain field; collision remains authoritative while the player crosses between its cell-centre waypoints.

defmethodinitialize-instance:after
&key

INITARG sharing above names the semantic initial value. Temporal state must own a distinct vector before either side is mutated.

let
position
walking-player-positionplayer
setf
walking-player-previous-positionplayer
vec3:make-vec3
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
defunmake-walking-player

Make the sanctuary player at the bridge's authored starting point.

make-instance'walking-player:positionposition:heading-xheading-x:heading-yheading-y:speedspeed
defunbegin-walking-player-frame
player

Retain player's exact pre-step pose for temporal rendering.

let
position
walking-player-positionplayer
previous
walking-player-previous-positionplayer
setf
vec3:vec3-xprevious
vec3:vec3-xposition
vec3:vec3-yprevious
vec3:vec3-yposition
vec3:vec3-zprevious
vec3:vec3-zposition
walking-player-previous-heading-xplayer
walking-player-heading-xplayer
walking-player-previous-heading-yplayer
walking-player-heading-yplayer
walking-player-previous-gaitplayer
walking-player-gaitplayer
player
defunwalking-player-clear-at-p
solidxybase-z

Whether the point-footprint player fits above base-z at X,Y.

let
cell-x
cell-y
loopforzfrom
floorbase-z
belowalways
zerop
defunwalking-player-support-height
sourcexycurrent-base-z

Return a nearby supported base height for a step to X,Y, or NIL.

The controller can climb one cubical step and descend two. It does not scan for a remote roof, so a wall cannot teleport the player onto its top.

let*
cell-x
cell-y
base
floorcurrent-base-z
loopforsupport-zfromdowntoforcandidate-base=
1+support-z
when
and
=1
collision-cell-occupancy-bitsolidcell-xcell-ysupport-z
walking-player-clear-at-psolidxycandidate-base
return
coercecandidate-base'single-float
defunwalking-player-standable-cell-p
sourcexyz

Whether the player can stand at the centre of the exact foot cell X,Y,Z.

defunmap-walking-cell-neighbors
functionsourcecellstart

Call function with each nearby eight-connected neighbor and step cost.

let*
dolist
offset'
01
10
0-1
-10
11
1-1
-1-1
-11
destructuring-bind
dxdy
offset
let*
next-x
+xdx
next-y
+ydy
next-z
walking-player-support-heightsource
+next-x0.5
+next-y0.5
z
when
andnext-z

A diagonal may pass beside a wall, but never through the point where two blocked cardinal cells meet.

or
zeropdx
zeropdy
handler-case
let
next
luft:make-sitedomainnext-xnext-y
roundnext-z
luft:+cell-extent+1
when
funcallfunctionnext
if
and
/=dx0
/=dy0
sqrt2.0
1.0
defunwalking-route-frontier-push
frontierprioritycostcell
vector-push-extend
listprioritycostcell
frontier
loopwithchild=
1-
lengthfrontier
while
pluspchild
forparent=
floor
1-child
2
while
<priority
first
areffrontierparent
do
rotatef
areffrontierchild
areffrontierparent
setfchildparent
frontier
defunwalking-route-frontier-pop
frontier
let
minimum
areffrontier0
last
vector-popfrontier
when
plusp
lengthfrontier
setf
areffrontier0
last
loopwithparent=0forleft=
1+
*2parent
while
<left
lengthfrontier
forright=
1+left
forchild=
if
and
<right
lengthfrontier
<
first
areffrontierright
first
areffrontierleft
rightleft
while
<
first
areffrontierchild
first
areffrontierparent
do
rotatef
areffrontierparent
areffrontierchild
setfparentchild
minimum
defunreconstruct-walking-route
parentsstartdestination
let
cellsnil
celldestination
loopuntil
=cellstart
do
pushcellcells
setfcell
gethashcellparents
cells
defunfind-walking-route
playersourcedestination

Return a bounded, eight-connected octile route from player to destination.

destination is a packed luft foot cell. A returned failed route retains the reason and search count, so a click that cannot be honored is inspectable rather than silently becoming a straight-line collision attempt.

let*
position
walking-player-positionplayer
labels
route
cellsstatusdetailvisits
make-instance'walking-route:startstart:destinationdestination:cellscells:statusstatus:detaildetail:visitsvisits
unless
nearby-walking-cell-pdestinationstart
return-fromfind-walking-route
routenil:failed"destination is outside the local path window"0
unless
return-fromfind-walking-route
routenil:failed"destination is not a clear supported cell"0
when
=startdestination
return-fromfind-walking-route
routenil:arrived"already there"0
let
parents
make-hash-table:test#'eql
costs
make-hash-table:test#'eql
frontier
make-array64:adjustablet:fill-pointer0
visits0
setf
gethashstartcosts
0.0
loopwhileforentry=forcost=
secondentry
forcell=
thirdentry
when
=cost
gethashcellcosts
do
incfvisits
when
=celldestination
return-fromfind-walking-route
route
reconstruct-walking-routeparentsstartdestination
:runningnilvisits
map-walking-cell-neighbors
lambda
nextstep-cost
let
next-cost
+coststep-cost
when
<next-cost
gethashnextcostsmost-positive-single-float
setf
gethashnextcosts
next-cost
gethashnextparents
cell
walking-route-frontier-pushfrontier
+next-cost
next-costnext
sourcecellstart
routenil:failed"no local walkable path reaches the destination"visits
defunstart-walking-player-route
playersourcexyz

Replace player's current intention with a route to foot cell X,Y,Z.

let*
destination
route
ifdestination
find-walking-routeplayersourcedestination
make-instance'walking-route:startnil:destinationnil:cellsnil:status:failed:detail"destination is outside the world"
setf
walking-player-routeplayer
route
route
defuncancel-walking-player-route
player&optional
detail"manual movement"

Return movement authority to direct input, retaining the cancelled route.

let
route
walking-player-routeplayer
when
androute
eq:running
walking-route-statusroute
setf
walking-route-statusroute
:cancelled
walking-route-detailroute
detail
player
defunwalking-player-reached-route-cell-p
playercell
let*
position
walking-player-positionplayer
horizontal-distance
sqrt
+
*dxdx
*dydy
defuntrim-walking-player-route
player
let
route
walking-player-routeplayer
when
androute
eq:running
walking-route-statusroute
loopwhile
and
walking-route-cellsroute
walking-player-reached-route-cell-pplayer
first
walking-route-cellsroute
do
pop
walking-route-cellsroute
unless
walking-route-cellsroute
setf
walking-route-statusroute
:arrived
walking-route-detailroute
"destination reached"
player
defunwalking-player-route-control
playercamera

Return camera-relative axes and remaining distance for player's route.

let
route
walking-player-routeplayer
when
androute
eq:running
walking-route-statusroute
let*
cell
first
walking-route-cellsroute
position
walking-player-positionplayer
direction-x
/dx
direction-y
/dy
yaw
values
+
*
cosyaw
direction-x
*
sinyaw
direction-y
-
*
sinyaw
direction-x
*
cosyaw
direction-y
distance
defuntry-walking-player-axis
playersourceaxisamount

Sweep player along one horizontal axis, sliding at blocked boundaries.

let*
position
walking-player-positionplayer

No substep spans a whole terrain cell, so an endpoint beyond a thin wall can never hide the occupied cell crossed to reach it.

step
/amountstep-count
x
vec3:vec3-xposition
y
vec3:vec3-yposition
z
vec3:vec3-zposition
clear-pt

Validate the whole axis attempt before publishing it. Axis separation therefore retains its atomic slide-at-a-wall behavior while every cell crossed by a long attempt still participates in collision.

looprepeatstep-countwhileclear-pdo
incfx
if
eqaxis:x
step0.0
incfy
if
eqaxis:y
step0.0
let
ifsupport
setfzsupport
setfclear-pnil
whenclear-p
setf
vec3:vec3-xposition
x
vec3:vec3-yposition
y
vec3:vec3-zposition
z
t
defuntry-walking-player-air-axis
playersourceaxisamount

Sweep one airborne horizontal axis while retaining solid wall collision.

let*
position
walking-player-positionplayer
step
/amountstep-count
x
vec3:vec3-xposition
y
vec3:vec3-yposition
clear-pt
looprepeatstep-countwhileclear-pdo
incfx
if
eqaxis:x
step0.0
incfy
if
eqaxis:y
step0.0
unless
setfclear-pnil
whenclear-p
setf
vec3:vec3-xposition
x
vec3:vec3-yposition
y
t
defunrequest-walking-player-jump
player

Request one grounded jump on player's next simulation step.

setf
walking-player-jump-requested-pplayer
t
player
defuncast-walking-player-spell
player

Ignite the staff orb; the renderer consumes this short cast envelope.

setf
walking-player-spell-flashplayer
1.0
player
defunthrow-walking-player-ball
playerorigindirection

Throw (or replace) player's ball from origin along direction.

let*
physics
walking-player-physicsplayer
old
walking-player-ball-handleplayer
setf
walking-player-ball-positionplayer
position
walking-player-previous-ball-positionplayer
vec3:make-vec3
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
walking-player-ball-handleplayer
luvcraft:spawn-physics-bodyphysics
vec3:vec3-xposition
vec3:vec3-zposition
vec3:vec3-yposition
:radius+thrown-ball-radius+:mass0.85:vx
*+thrown-ball-speed+
vec3:vec3-xdirection
:vy
*+thrown-ball-speed+
vec3:vec3-zdirection
:vz
*+thrown-ball-speed+
vec3:vec3-ydirection
:restitution0.72:friction0.58:rolling-resistance0.018:damping0.035:lifetime8.0
walking-player-spell-flashplayer
1.0
player
defunpost-walking-player-ball-terrain
playersource

Publish nearby luft cells to the Luvcraft solver as static boxes.

let*
physics
walking-player-physicsplayer
position
walking-player-ball-positionplayer
whenposition
let
cx
floor
vec3:vec3-xposition
cy
floor
vec3:vec3-yposition
cz
floor
vec3:vec3-zposition
loopforxfrom
-cx2
to
+cx2
do
loopforyfrom
-cy2
to
+cy2
do
loopforzfrom
-cz2
to
+cz2
whendo
luvcraft:post-physics-boxphysicsxzy
1+x
1+z
1+y
player
defunsync-walking-player-ball
player
let
physics
walking-player-physicsplayer
handle
walking-player-ball-handleplayer
if
multiple-value-bind
xzy
let
position
walking-player-ball-positionplayer
setf
vec3:vec3-xposition
x
vec3:vec3-yposition
y
vec3:vec3-zposition
z
setf
walking-player-ball-handleplayer
nil
walking-player-ball-positionplayer
nil
walking-player-previous-ball-positionplayer
nil
player
defunadvance-walking-player-ball
playersourceseconds
let
position
walking-player-ball-positionplayer
whenposition
let
previous
walking-player-previous-ball-positionplayer
setf
vec3:vec3-xprevious
vec3:vec3-xposition
vec3:vec3-yprevious
vec3:vec3-yposition
vec3:vec3-zprevious
vec3:vec3-zposition
incf
walking-player-physics-clockplayer
seconds
looprepeat3while
>=
walking-player-physics-clockplayer
/1d060d0
do
decf
walking-player-physics-clockplayer
/1d060d0
luvcraft:step-physics-world
walking-player-physicsplayer
/1.060.0
when
>=
walking-player-physics-clockplayer
/1d060d0
setf
walking-player-physics-clockplayer
0d0
player
defunadvance-walking-player-vertical
playersourceseconds

Apply Luvcraft-strength gravity to luft's Z-up walking controller.

let*
position
walking-player-positionplayer
jump-p
walking-player-jump-requested-pplayer
setf
walking-player-jump-requested-pplayer
nil
when
andjump-p
walking-player-grounded-pplayer
setf
walking-player-vertical-velocityplayer
+walking-player-jump-speed+
walking-player-grounded-pplayer
nil
unless
walking-player-grounded-pplayer
incf
walking-player-vertical-velocityplayer
incf
vec3:vec3-zposition
*
walking-player-vertical-velocityplayer
seconds
let
support
walking-player-support-heightsource
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
when
andsupport
<=
walking-player-vertical-velocityplayer
0.0
<=
vec3:vec3-zposition
support
setf
vec3:vec3-zposition
support
walking-player-vertical-velocityplayer
0.0
walking-player-grounded-pplayer
t
player
defunadvance-walking-player
playersourcecameraforwardrightseconds&keymaximum-distance

Advance player from camera-relative movement axes for seconds.

let
length
sqrt
+
*forwardforward
*rightright
if
plusplength
let*
forward
/forwardlength
right
/rightlength
yaw
direction-x
+
*
cosyaw
forward
*
sinyaw
right
direction-y
+
*
sinyaw
forward
*
-
cosyaw
right
distance
min
*seconds
walking-player-speedplayer
ormaximum-distancemost-positive-single-float
position
walking-player-positionplayer
before-x
vec3:vec3-xposition
before-y
vec3:vec3-yposition
if
walking-player-grounded-pplayer
progn
try-walking-player-axisplayersource:x
*direction-xdistance
try-walking-player-axisplayersource:y
*direction-ydistance
progn
try-walking-player-air-axisplayersource:x
*direction-xdistance
try-walking-player-air-axisplayersource:y
*direction-ydistance
let*
dx
-
vec3:vec3-xposition
before-x
dy
-
vec3:vec3-yposition
before-y
travelled
sqrt
+
*dxdx
*dydy
when
plusptravelled
setf
walking-player-heading-xplayer
/dxtravelled
walking-player-heading-yplayer
/dytravelled

The shader's authored half-step is 0.75 cells. Advancing gait from actual travelled distance keeps planted feet in world space even when collision shortens a requested move.

incf
walking-player-gaitplayer

A released key owes the character a planted-foot pose rather than leaving one boot suspended forever at an arbitrary sampled phase.

let*
gait
walking-player-gaitplayer
target
*pi
round
/gaitpi
difference
-targetgait
maximum-change
*9.0seconds
incf
walking-player-gaitplayer
max
-maximum-change
minmaximum-changedifference
advance-walking-player-ballplayersourceseconds
setf
walking-player-spell-flashplayer
max0.0
-
walking-player-spell-flashplayer
*2.4seconds
player
defunsoft-follow-step
currenttargetsecondsquiet-radius

Move current calmly toward target, catching up harder as separation grows.

quiet-radius is a soft camera zone: motion inside it is treated as local character movement rather than a reason to reframe. Outside it, an exact exponential response makes the result independent of frame rate, while its rate rises with distance so large discontinuities do not leave the player behind.

let*
difference
-targetcurrent
distance
absdifference
excess
max0.0
-distancequiet-radius
if
zeropexcess
current
let*
rate
+1.8
*0.9excess
blend
-1.0
exp
-
*rateseconds
+current
*
signumdifference
excessblend
defunfollow-walking-player
cameraplayer&keyseconds

Follow player through a quiet zone with distance-sensitive catch-up.

multiple-value-bind
rightupforward
declare
ignorerightup
let*
player-position
walking-player-positionplayer
heading-x
walking-player-heading-xplayer
heading-y
walking-player-heading-yplayer

Showing more of where the traveler is going makes movement legible.

aim-x
+
vec3:vec3-xplayer-position
*2.4heading-x
aim-y
+
vec3:vec3-yplayer-position
*2.4heading-y
aim-z
+
vec3:vec3-zplayer-position
1.45
target-x
-aim-x
*distance
vec3:vec3-xforward
target-y
-aim-y
*distance
vec3:vec3-yforward
target-z
-aim-z
*distance
vec3:vec3-zforward
camera-position
camera-positioncamera
if
nullseconds
setf
vec3:vec3-xcamera-position
target-x
vec3:vec3-ycamera-position
target-y
vec3:vec3-zcamera-position
target-z
setf
vec3:vec3-xcamera-position
soft-follow-step
vec3:vec3-xcamera-position
target-xseconds0.28
vec3:vec3-ycamera-position
soft-follow-step
vec3:vec3-ycamera-position
target-yseconds0.28

Terrain relief is much less important than lateral travel: let a whole stair tread pass without bobbing the frame.

vec3:vec3-zcamera-position
soft-follow-step
vec3:vec3-zcamera-position
target-zseconds0.85
camera
defunwalking-player-render-lanes
player

Return current, previous, and heading vec4 lanes for the GPU boundary.

labels
position-lane
list
vec3:vec3-xposition
vec3:vec3-yposition
+
vec3:vec3-zposition
1.48
gait
values
position-lane
walking-player-positionplayer
walking-player-gaitplayer
position-lane
walking-player-previous-positionplayer
walking-player-previous-gaitplayer
list
walking-player-heading-xplayer
walking-player-heading-yplayer
walking-player-previous-heading-xplayer
walking-player-spell-flashplayer
let
position
walking-player-ball-positionplayer
ifposition
list
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
+thrown-ball-radius+
'
0.00.00.00.0
let
position
walking-player-previous-ball-positionplayer
ifposition
list
vec3:vec3-xposition
vec3:vec3-yposition
vec3:vec3-zposition
+thrown-ball-radius+
'
0.00.00.00.0