game/vicar.module — THE HOLLOW VICAR.
One model, built properly. The thing on every screenshot.
Everything here is generated: there is no imported mesh. The figure is a
set of lofted surfaces — a cassock whose skirt pools and drags instead of
ending in feet, a mozzetta over hunched and uneven shoulders, a narrow
hood with real thickness and a dark interior, arms a foot too long ending
in gaunt seven-jointed hands — plus the cold blue-green light where a face
should be, which carries its own point light because the fog eats anything
that does not.
THREE THINGS THIS FILE OWES THE REST OF THE GAME
1. An entity named `HollowVicar` under the `Vicar` container. That name is
the contract: the hunt slice finds it and drives its position and
rotation. Nothing here moves it across the ground.
2. Everything below that root animates ITSELF from the root's motion —
breathing at rest, a drifting walk, a faster stalk, and a flare when it
takes an interest — so hunt only has to say where the thing is.
3. Every entity marked temporary, so the scene never bakes it.
WHY THE GEOMETRY IS BUILT THE WAY IT IS
Silhouette first. At thirty metres in fog this is a black shape, and the
shape has to be wrong before any of the detail matters: too tall, too
narrow, no feet, a hem wider than the shoulders, arms hanging past the
knees, and a head that comes to a point in front of where a skull would be.
Then the detail. Every surface carries three baked per-vertex channels the
`vicar_wool` shader reads:
vertex_color.r height, 0 at the hem lying in the water → 1 at the crown
vertex_color.g baked ambient occlusion
vertex_color.b fold depth, 0 on a crest → 1 in a valley
and UVs laid in approximate METRES, so the weave is one physical size on
the sleeve and on the skirt.
newMesh( ) → void
1. MESH CONSTRUCTION
A tiny loft kernel. Every part of the figure is built from it.
Winding is decided per triangle rather than assumed: the engine's meshes
are wound so that cross(b-a, c-a) points OUT of the surface (verified
against @builtin::meshes.sphere, 401/401 triangles), so each triangle is
emitted in whichever order agrees with the outward hint carried on its
vertices. That makes a shell with an inverted interior surface — the hood —
come out right without a second code path.
vert(m: ?, x: ?, y: ?, z: ?, hx: ?, hy: ?, hz: ?, u: ?, v: ?, cr: ?, cg: ?, cb: ?) → void
Returns a ZERO-BASED vertex index, which is what the geometry format takes.
| arg | type | description |
|---|
| m | ? | |
| x | ? | |
| y | ? | |
| z | ? | |
| hx | ? | |
| hy | ? | |
| hz | ? | |
| u | ? | |
| v | ? | |
| cr | ? | |
| cg | ? | |
| cb | ? | |
tri(m: ?, a: ?, b: ?, c: ?) → void
| arg | type | description |
|---|
| m | ? | |
| a | ? | |
| b | ? | |
| c | ? | |
quad(m: ?, a: ?, b: ?, c: ?, d: ?) → void
| arg | type | description |
|---|
| m | ? | |
| a | ? | |
| b | ? | |
| c | ? | |
| d | ? | |
stitch(m: ?, rings: ?) → void
Stitch consecutive rings. Each ring is an array of S+1 indices where the
last repeats the first at a full-circumference u, so the weave has no
interpolation seam.
| arg | type | description |
|---|
| m | ? | |
| rings | ? | |
capRing(m: ?, ring: ?, cx: ?, cy: ?, cz: ?, hx: ?, hy: ?, hz: ?, u: ?, v: ?, cr: ?, cg: ?, cb: ?) → void
| arg | type | description |
|---|
| m | ? | |
| ring | ? | |
| cx | ? | |
| cy | ? | |
| cz | ? | |
| hx | ? | |
| hy | ? | |
| hz | ? | |
| u | ? | |
| v | ? | |
| cr | ? | |
| cg | ? | |
| cb | ? | |
finalize(m: ?) → void
Smooth normals from the (now consistently wound) faces, plus a tangent
basis derived from the UVs so the shader's cloth relief has a direction.
foldField(theta: ?, s: ?) → void
2. THE CLOTH FUNCTIONS
One fold field, sampled by every woollen part, so the hood's creases and
the skirt's are recognisably the same fabric. Three harmonics: the big
drape, the crease inside it, and the crumple on top.
| arg | type | description |
|---|
| theta | ? | |
| s | ? | |
loftRing(m: ?, sec: ?) → void
Loft a run of sections into a closed tube.
section = {
c = {x,y,z} ring centre
ax = {x,y,z} the ring's "sideways" axis, ALREADY scaled by radius
az = {x,y,z} the ring's "forward" axis, ALREADY scaled by radius
fold = number radial fold amplitude in metres
v = number the v texture coordinate (arc length along the loft)
ao = number baked ambient occlusion for the whole ring
inv = boolean? point the outward hint INWARD (for a shell's interior)
warp = function? (theta, section) -> extraRadialScale, extraOffsetY
}
| arg | type | description |
|---|
| m | ? | |
| sec | ? | |
resample(profile: ?, sub: ?) → void
Resample a { y, z, rx, rz } profile into `sub` times as many sections,
interpolating everything, so the fold field is sampled finely enough to
read as cloth rather than as a fan of facets.
| arg | type | description |
|---|
| profile | ? | |
| sub | ? | |
buildSleeve(profile: ?, side: ?, opts: ?) → void
A sleeve, lofted along its own bend. Local origin is the joint it swings
from, so the animation is a rotation and nothing else.
| arg | type | description |
|---|
| profile | ? | |
| side | ? | |
| opts | ? | |
buildHand(side: ?) → void
Long, thin, and jointed. This is where a close capture is won or lost:
everything above is drapery, and drapery can be faked, but a hand cannot.
tube(pts: ?, ao: ?, baseY: ?) → void
Local loft for the small round parts. Sixty-four segments on a finger
would be four thousand triangles per digit for nothing.
| arg | type | description |
|---|
| pts | ? | |
| ao | ? | |
| baseY | ? | |
dome(ring: ?, q: ?, dir: ?) → void
Round both ends off.
| arg | type | description |
|---|
| ring | ? | |
| q | ? | |
| dir | ? | |
buildClasp( ) → void
The only metal on the figure, and the only thing on it that was ever
polished. A tarnished ring with a drop hanging out of it, at the throat.
buildFace( ) → void
A small flattened ellipsoid. u = 0 faces forward, and the glow shader puts
its bright core on the equator at u = 0, so the light points out of the
hood while the poles stay dark.
buildTatters( ) → void
Strands of the hem that have come away and now drag behind it in the
water. They are what makes the thing read as MOVING when it is barely
moving: they are the only part of the silhouette that trails.
meshFor(name: ?, make: ?) → void
| arg | type | description |
|---|
| name | ? | |
| make | ? | |
spawn(name: ?, parent: ?, pos: ?) → void
| arg | type | description |
|---|
| name | ? | |
| parent | ? | |
| pos | ? | |
addModel(e: ?, meshName: ?, make: ?, material: ?, shadows: ?) → void
| arg | type | description |
|---|
| e | ? | |
| meshName | ? | |
| make | ? | |
| material | ? | |
| shadows | ? | |
triangleCount( ) → number
alive(e: ?) → void
An entity proxy held across frames goes stale the moment something
despawns it, and then EVERY read on it raises. This module holds a dozen
of them, so one guard on the root — which every other part hangs off —
is what stands between a scene reload and an exception per frame forever.
flare(amount: number) → void
| arg | type | description |
|---|
| amount | number | |
update(dt: number) → void
| arg | type | description |
|---|
| dt | number | |
report( ) → void
A debug handle. `require("game.vicar").report()` from an execute call.
invalidate( ) → void
Drop the cached GPU meshes so the next build regenerates them. Only for
authoring: it is how a geometry edit is seen without reloading the world.
The GPU resources are FREED, not just forgotten. `renderer.mesh.create`
mints a new one every call, so a cache that is merely emptied leaks about
700 KB of vertex buffers per rebuild and there is nothing that enumerates
live handles afterwards to find them again.