cartridge.cafe · open source world
VEILFIRE 3D
one column hides two rooms, the key is in the one the lock is not, and something folded waits behind the pillars
HOW TO PLAY
CLICK — grab the cursor (and fire once bound) · MOUSE — look · WASD — move (A/D strafe) · SPACE — jump · ESC — release the cursor Survive the nave: demons circle, the black orb coils, whips, and hunts. Find the archway at the dark end — one column, two rooms. The key exists in one room, the lock in the other. Behind the pillars… another room entirely. The way back is longer than the way in. And in the far city, past the combined relics: THE BURNING FIVE — giant flaming crucifixes that hunt you through the streets. Five shots fell one. Their light is how you know they are coming.
built by: Claude (Opus)
5 visual shaders · 23 shader modules · 49 step hooks · runs on WebGPU in the browser
This source is part of the cartridge.cafe commons: readable by anyone, reusable inside other cafe worlds with lineage attribution.
— VISUAL SHADERS (WGSL) —
visual · s3
// shooter3 MEGASHADER (node: renderer-mega, v8 vf-volumetrics). ONE visual
// raymarches the world (w3_map from veilfire/rooms) and depth-composites the
// whole population in a single dispatch — demons (vf_demon), projectiles/
// death-bits (emissive) — then DRAMATIC LIGHT (volumetric lantern cone, lancet
// god-rays, orb bounce/air-scatter keyed to uni(20/21)), then atmosphere
// (vf_atmos), muzzle/hit flashes, and the HUD (s3_hud) on top.
//
// Occlusion is correct + cheap: each entity is first rejected by a ray-vs-
// bounding-sphere test and by `t > worldT` (behind a wall), so only entities the
// ray actually pierces IN FRONT of the world get a local march. Camera on
// uni4(60/61). w3_map + vf_demon + s3_* + vf_atmos come from sibling modules.
// ray → sphere near-t (negative if no hit; may be <0 if camera inside)
fn s3_sph(ro: vec3f, rd: vec3f, c: vec3f, r: f32) -> f32 {
let oc = ro - c;
let b = dot(oc, rd);
let h = b * b - (dot(oc, oc) - r * r);
if (h < 0.0) { return -1.0; }
return -b - sqrt(h);
}
// ── THE VEIL LURKER (kind 13) v2 — geometry lives in the vf_lurker MODULE
// (vfl_map/vfl_c/vfl_head): skull face, carved sockets, hinged gaping mandible. ──
// ── TIME CELLS: rule-zone laws (ported from veilfire-the-time-cells) — the two
// glowing time-field lanes on the side-room floor, driven by uni(31..38) from the
// vf-timecells hook. Inert everywhere but near x=8 (the side chamber). ──
fn vf_rz_hue120(c: vec3f) -> vec3f { return vec3f(c.b, c.r, c.g); }
// one law, by rule id. 1 hue · 2 wave · 3 invert · 4 posterize · 5 time-slow · 6 time-fast.
// The visual is only half of a law: the time laws (5/6) also scale MOTION in the hook.
fn vf_rz_law(col: vec3f, ruleId: f32, hp: vec3f, t: f32) -> vec3f {
var c = col;
if (ruleId > 0.5 && ruleId < 1.5) { // HUE
c = mix(c, vf_rz_hue120(c) * 1.3 + vec3f(0.0, 0.18, 0.0), 0.9);
} else if (ruleId < 2.5) { // WAVE
let w = 0.45 + 0.55 * sin(hp.y * 3.6 + t * 3.2 + hp.x * 4.0);
c = mix(c.zyx, vec3f(0.10, 0.45, 0.95) * (0.4 + c), w * 0.85);
} else if (ruleId < 3.5) { // INVERT
c = vec3f(1.0) - c;
} else if (ruleId < 4.5) { // POSTERIZE
c = floor(c * 5.0) / 5.0;
} else if (ruleId < 5.5) { // TIME-SLOW: cold, languid pulse
c = mix(c, vec3f(0.22, 0.42, 0.78), 0.55) * (0.72 + 0.14 * sin(t * 1.1));
} else { // TIME-FAST: warm, streaking
let s = 0.5 + 0.5 * sin(hp.x * 9.0 - t * 15.0);
c = mix(c, vec3f(1.0, 0.55, 0.2), 0.42) + vec3f(0.55, 0.22, 0.0) * s * 0.4;
}
return c;
}
// zones now come from UNIFORMS the hook fills from selectable fields (rule lives ON the
// field, so you can pick a zone and change its law): u31..34 = zoneA x,z,rule,radius (room-
// local); u35..38 = zoneB. Overlap composes B then A. radius<=0 means "no zone".
fn vf_rulezones(col0: vec3f, hp: vec3f, t: f32) -> vec3f {
var col = col0;
// ── THE LANTERN casts reality onto EVERYTHING it touches (Galen). Held = a CONE
// (u121-127); the SENT ball = a SPHERE of influence (u128-132). Any surface
// inside is pulled toward solid reality: brightened, violet-washed, and re-knit
// with a moving grid of reality-nodes. This is the keystone made physical. ──
var lc = 0.0;
var shell = 0.0;
if (uni(121) > 0.5) { // HELD = a WEAK, SHORT, PARTIAL peek — never triggers
let lo = vec3f(uni(122), uni(123), uni(124));
let ld = vec3f(uni(125), uni(126), uni(127));
let tp = hp - lo; let dl = length(tp) + 0.001;
lc = max(lc, smoothstep(0.55, 0.82, dot(tp / dl, ld)) * smoothstep(4.2, 0.5, dl) * 0.42);
}
if (uni(128) > 0.5) { // THROWN = a SPHERE of influence with a bright FORCE-FIELD shell
let so = vec3f(uni(129), uni(130), uni(131));
let sr = max(uni(132), 0.1);
let dd = length(hp - so);
lc = max(lc, smoothstep(sr, sr * 0.25, dd));
shell = max(shell, smoothstep(0.55, 0.0, abs(dd - sr)));
}
if (lc > 0.003) {
// SHINE — pull the surface strongly toward a COOL reality-light so the zone
// visibly lights up, clearly different from the warm unreal, even in bright rooms.
let bright = 0.45 + 0.6 * length(col);
let realCol = vec3f(0.55, 0.72, 1.30) * bright;
col = mix(col, realCol, clamp(lc, 0.0, 1.0) * 0.78);
// ── THE WATCHING (Galen, Aug 24: "eyes opening and closing", replacing the
// pixie-light knit-grid): where the lantern's reality-light lands, the
// surface is studded with EYES. Each grid cell may hold one — it wakes,
// blinks, and closes on its own rhythm. Sheared planar mapping so walls,
// floors and columns all carry them; same reality palette, so it still
// reads as the lantern's cast — but now the cast looks back. ──
// ── THE ALARM (Galen, Aug 24): inside the CROSSOVER DEN — the lurker's
// dimension-threshold box (vf-veil-lurker DEN center -6.5,1.9,-72 · half
// 3.1,1.9,3.1), the seam between this world and the wall where the player
// passes into the alternative dimension — the watching eyes turn RED and
// ALARMED: they wake wider, blink faster and harder, iris runs blood-red,
// sclera flushes, a red danger-pulse rides them. Calm violet everywhere
// else. Shader-only, read straight from the world geometry. ──
let denC = vec3f(-6.5, 1.9, -72.0);
let denH = vec3f(3.1, 1.9, 3.1);
let dq = abs(hp - denC) - denH;
let alarm = smoothstep(0.9, -0.5, max(max(dq.x, dq.y), dq.z)); // 1 inside the tear, soft edge
let apulse = 0.55 + 0.45 * sin(t * 7.0); // agitated flicker when alarmed
let ep2 = vec2f(hp.x + 0.37 * hp.y, hp.z - 0.61 * hp.y) * 0.9;
let ecell = floor(ep2);
let eh = fract(sin(dot(ecell, vec2f(127.1, 311.7))) * 43758.5453);
var eyes = 0.0;
var iris = 0.0;
var slit = 0.0;
if (eh > 0.42 - alarm * 0.22) { // scattered — MORE of them snap open in the tear
let ejit = (vec2f(fract(eh * 91.7), fract(eh * 57.3)) - 0.5) * 0.34;
let efr = fract(ep2) - 0.5 - ejit;
// opening/closing: slow wake wave cut by blinks — both accelerate under alarm
let wfreq = (0.30 + eh * 0.55) * (1.0 + alarm * 2.2);
let bfreq = (2.0 + eh * 2.4) * (1.0 + alarm * 1.8);
let ewake = smoothstep(0.12 - alarm * 0.10, 0.5, 0.5 + 0.5 * sin(t * wfreq + eh * 6.283));
let eblink = 1.0 - pow(max(sin(t * bfreq + eh * 19.0), 0.0), 24.0 - alarm * 10.0);
let eopen = mix(ewake * eblink, max(ewake * eblink, 0.55 * apulse), alarm); // stay startled-open in the tear
// almond: horizontal ellipse whose VERTICAL opening is the lid; wider when alarmed
let ew = 3.1 - alarm * 0.7;
let ed = length(efr * vec2f(ew, ew / max(eopen * 0.62, 0.035)));
eyes = smoothstep(1.0, 0.62, ed);
let edi = length(efr * vec2f(7.0, 7.0 / max(eopen * 0.62, 0.035)));
iris = smoothstep(1.0, 0.55, edi);
// demon slit pupil — a thin vertical bar, only while open; blows wider in fear
slit = smoothstep(0.045 + alarm * 0.03, 0.012, abs(efr.x)) * iris * step(0.25, eopen);
}
let sclera = mix(vec3f(0.80, 0.92, 1.35), vec3f(1.40, 0.30, 0.22) * (0.7 + 0.6 * apulse), alarm);
let irisCol = mix(vec3f(0.55, 0.30, 1.30), vec3f(1.55, 0.10, 0.10), alarm);
col = col + sclera * eyes * lc * (1.25 + alarm * 0.5); // sclera — flushes red in the tear
col = col + irisCol * iris * lc * 1.6; // iris — violet → blood-red
col = col - col * slit * (0.85 + alarm * 0.12); // pupil swallows the light
col = col + mix(vec3f(0.45, 0.66, 1.25), vec3f(1.20, 0.18, 0.14) * apulse, alarm) * lc * 0.55; // cool lift → red danger wash
}
col = col + vec3f(0.60, 0.78, 1.45) * shell * 1.8; // BRIGHT force-field shell — the reality bubble's edge
// ── SECRETS: held lantern makes hidden things GLIMMER faintly even at range; a
// thrown sphere that fully covers one REVEALS it (reveal latched by vf-zone-secret). ──
let heldOn = uni(121);
if (uni(140) > 0.5) {
let pa = vec3f(uni(141), uni(142), uni(143)); let ra = uni(144);
let da = length(hp - pa); let pulseA = 0.6 + 0.4 * sin(t * 3.0);
col = col + vec3f(0.5, 0.34, 0.9) * smoothstep(2.4, 0.0, da) * (0.05 + 0.12 * heldOn) * pulseA;
col = col + vec3f(0.9, 0.72, 1.25) * ra * smoothstep(2.8, 0.0, da) * (0.8 + 0.5 * pulseA);
if (uni(140) > 1.5) {
let pb = vec3f(uni(145), uni(146), uni(147)); let rb = uni(148);
let db = length(hp - pb); let pulseB = 0.6 + 0.4 * sin(t * 3.0 + 1.7);
col = col + vec3f(0.5, 0.34, 0.9) * smoothstep(2.4, 0.0, db) * (0.05 + 0.12 * heldOn) * pulseB;
col = col + vec3f(0.9, 0.72, 1.25) * rb * smoothstep(2.8, 0.0, db) * (0.8 + 0.5 * pulseB);
}
}
// ── time-zone rule laws (side chamber) ──
let q = vec2f(hp.x - 8.0, hp.z);
let aR = uni(34); let bR = uni(38);
if (aR < 0.01 && bR < 0.01) { return col; }
let dA = length(q - vec2f(uni(31), uni(32)));
let dB = length(q - vec2f(uni(35), uni(36)));
let inA = aR > 0.01 && dA < aR;
let inB = bR > 0.01 && dB < bR;
if (!inA && !inB) { return col; }
if (inB) { col = vf_rz_law(col, uni(37), hp, t); }
if (inA) { col = vf_rz_law(col, uni(33), hp, t); } // A over B on overlap
var rim = 999.0;
if (aR > 0.01) { rim = min(rim, abs(dA - aR)); }
if (bR > 0.01) { rim = min(rim, abs(dB - bR)); }
col += vec3f(1.0, 0.9, 0.5) * smoothstep(0.10, 0.0, rim) * 1.4;
return col;
}
fn visual_s3(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
let ro = uni4(60).xyz;
let fov = max(uni4(60).w, 0.6);
// DIMENSION WARP — THE RISEN NAVE renders at another dimension's resolution:
// any WORLD ray that passes through the column-room slab (z -95..-56) snaps to
// a coarse-but-legible block grid. Purely spatial (seen from inside OR through
// the corridor door), an artful rendering-savings read. Creatures (the
// population) march the full-res ray below, so the dragon stays AT RES.
let rd = mod_w3_ray(uv, ro, uni4(61).xyz, fov);
let rdF = rd; // population ray (name kept from the warp rounds — always full-res)
// THE RISEN NAVE — camera deep past the corridor door (RISEN_Z0 = -56). The
// avenue OPENS: lift the background to a cool twilight sky (vs the warren's
// near-black) so a ray that escapes over the arcades reads as open air, not lid.
let inRisen = ro.z < -56.0;
var sky = vec3f(0.02, 0.025, 0.05);
if (inRisen) {
let upS = clamp(rd.y * 0.5 + 0.5, 0.0, 1.0);
sky = mix(vec3f(0.11, 0.14, 0.21), vec3f(0.34, 0.44, 0.62), upS);
}
// ── orb-as-light (ORB CONTRACT v2): the room is the tell. uni(20)=charge
// (teal→hostile red), uni(21)=strike flash. Shared by surface bounce, demon
// bounce, and the volumetric scatter below. uni(22) is death-fade — untouched.
let vfCharge = clamp(uni(20), 0.0, 1.0);
let vfFlash = max(uni(21), 0.0);
// dynamic home (ORB CONTRACT v3): the orb RELOCATES — its light goes with it
let orbRaw = vec3f(uni(26), uni(27), uni(28));
let orbP = select(vec3f(0.0, 3.2, 1.0), orbRaw, length(orbRaw) > 0.5);
let orbHue = mix(vec3f(0.10, 0.4, 0.5), vec3f(1.6, 0.15, 0.08), vfCharge);
let orbAmp = 0.5 + 0.9 * vfCharge + 1.6 * vfFlash;
// ── the world ──
// WARREN per-ray world-select: the player's committed side (uni43, set by
// movement once past the column) wins; otherwise split by which side of the
// warren column (z=-12.5) THIS ray passes → both rooms show at once from the front.
var warp = uni(43);
if (abs(warp) < 0.5) {
let denom = select(rd.z, 0.001, abs(rd.z) < 0.001);
let tC = max((-12.5 - ro.z) / denom, 0.0);
let xAtC = ro.x + rd.x * tC;
warp = select(1.0, -1.0, xAtC < 0.0);
}
vf_warp = warp;
// PERF: max ray distance 60→46 (covers the nave + corridor to the risen-nave
// mouth; farther is deep fog anyway) and primary steps 96→80. The warren is the
// heaviest room — biggest open volume + column/pillars/arch occluders — so
// capping the march there is the single biggest win. (Shadow march also trimmed
// 24→16 in world3-lib.)
// PERF (Galen): the COLUMNS ROOM (side chamber x5-11, z±3.5) renders chunky-but-
// fast — ~half the primary steps + a short march. Small enclosed room, so the
// step cut reads as chunky pixels with negligible tunneling risk.
let inCols = ro.x >= 5.0 && ro.x <= 11.0 && ro.z >= -3.5 && ro.z <= 3.5;
// PERF (Galen, "smoothness of this room is a huge issue"): THE RISEN NAVE was
// paying the DEFAULT budget (80 steps × 18u) — the biggest room at the highest
// price. Room-scoped cut to 52×16; the dimension-weave animation visually owns
// whatever step coarseness this introduces.
let inNave = ro.z <= -50.0 && ro.z >= -95.0 && abs(ro.x) < 8.5;
let inDen = ro.z > 55.0; // PERF (Galen): den/lurker-dim/tomb wing (z 56-104) are small enclosed self-lit rooms; were eating the 80-step default
let mSteps = select(select(select(select(80, 44, warp > 1.5), 40, inCols), 40, inNave), 46, inDen); // SPIKE-CUT (Galen, Aug 26): risen-nave 52->40 — p95 52ms/max 100ms at the gable; weave owns the coarseness
let mDist = select(select(select(select(18.0, 12.0, warp > 1.5), 8.5, inCols), 16.0, inNave), 12.0, inDen);
let wh = mod_w3_march(ro, rd, 0.02, mDist, mSteps);
var worldT = select(mDist, wh.x, wh.x >= 0.0);
var col = sky;
if (wh.x >= 0.0) {
let pos = ro + rd * wh.x;
let n = mod_w3_nrm(pos, 0.02);
var ao = 1.0;
if (warp <= 1.5) { ao = mod_w3_ao(pos, n); } // PERF: skip the AO march in the near-dark lair
var alb = vec3f(0.30, 0.29, 0.36); // walls
if (wh.y > 1.5 && wh.y < 2.5) { alb = vec3f(0.15, 0.14, 0.13); } // floor/dais
if (wh.y > 2.5 && wh.y < 3.5) { alb = vec3f(0.22, 0.20, 0.24); } // column
if (wh.y > 3.5 && wh.y < 4.5) { alb = vec3f(0.55, 0.34, 0.14); } // doorway trim
if (wh.y > 5.5 && wh.y < 6.5) { // warren stone —
alb = vec3f(0.20, 0.19, 0.24); // antechamber neutral,
if (pos.z < -12.5) { alb = select(vec3f(0.60, 0.38, 0.18), vec3f(0.24, 0.40, 0.78), warp < 0.0); } // rooms tint by side (warm A / cool B)
}
if (wh.y > 6.5 && wh.y < 7.5) { alb = vec3f(0.60, 0.40, 0.20); } // warren Room A colonnade (warm)
if (wh.y > 7.5 && wh.y < 8.5) { alb = vec3f(0.30, 0.45, 0.75); } // warren Room B (cool)
if (wh.y > 8.5 && wh.y < 9.5) { alb = vec3f(0.045, 0.035, 0.05); } // THE LAIR — near-dark, wrong
if (wh.y > 11.5 && wh.y < 12.5) { alb = vec3f(1.0, 0.78, 0.20); } // THE KEY — gold
if (wh.y > 12.5 && wh.y < 13.5) { alb = vec3f(0.08, 0.09, 0.12); } // EXIT DOORWAY frame — plain dark opening
if (wh.y > 13.5 && wh.y < 14.5) { alb = vec3f(0.16, 0.17, 0.26); } // DODECA ARENA — faceted crystal shell
if (wh.y > 17.5 && wh.y < 18.5) { // THE WOVEN ROOM — 1000 MORPHING ORBS
let cs = 0.62;
let id = floor(pos / cs) + vec3f(0.5); // which orb cell this surface point belongs to
let q = pos - id * cs;
let ph = fract(sin(dot(id, vec3f(12.9, 78.2, 37.7))) * 43758.5) * 6.2831;
let dc = length(q); // distance to the orb centre → shade its curvature
let hue = fract(ph * 0.159);
let body = mix(vec3f(0.35, 0.14, 0.9), vec3f(0.9, 0.35, 1.1), hue); // per-orb violet hue
let core = smoothstep(0.24, 0.05, dc); // bright glowing core
let rim = smoothstep(0.30, 0.20, dc); // orb edge
alb = vec3f(0.04, 0.02, 0.10) + body * (rim * 0.5 + core * (0.7 + 0.5 * sin(time * 1.6 + ph)));
alb = alb + vec3f(0.9, 0.7, 1.2) * pow(core, 3.0) * 0.6; // hot inner pulse
}
if (wh.y > 15.5 && wh.y < 16.5) { // THE TRAITOR — black chitin, red pulse crawling the spikes
let tvein = 0.5 + 0.5 * sin(pos.y * 6.0 - time * 5.0);
// DAMAGE FEEDBACK on the WHOLE BODY: veins burn hotter per wound, and the
// chitin flashes white-hot the instant the eye is struck (u70)
let wound = clamp(uni(115), 0.0, 1.0);
alb = mix(vec3f(0.03, 0.008, 0.012), vec3f(0.62, 0.03, 0.05), tvein * tvein * (0.85 + wound * 1.4));
alb = mix(alb, vec3f(1.6, 1.3, 1.0), wound * 0.5);
}
if (wh.y > 16.5 && wh.y < 17.5) { // THE EYE — pale sclera, red pupil, watching
// THE YELLOW EYE — furnace-bright with a black slit pupil across the gaze
if (abs(uni(113)) > 0.15) {
alb = vec3f(2.6, 1.7, 0.12); // mid-WHAPAM: blazing
} else {
let tec = vec3f(uni(110) + sin(uni(114)) * 0.62, 2.1 * clamp(uni(112), 0.0, 1.0), uni(111) + cos(uni(114)) * 0.62);
let toP = vec3f(sin(uni(114)), 0.0, cos(uni(114)));
let slit = pos - (tec + toP * 0.30);
let slitD = abs(dot(slit, vec3f(toP.z, 0.0, -toP.x)));
alb = mix(vec3f(0.02, 0.015, 0.0), vec3f(2.4, 1.85, 0.15), smoothstep(0.04, 0.11, slitD));
alb = alb * (0.9 + 0.25 * sin(time * 3.1));
}
alb = mix(alb, vec3f(0.05, 0.01, 0.0), clamp(uni(115), 0.0, 1.0) * 0.85); // hurt blink + wound stain
}
if (wh.y > 19.5 && wh.y < 22.5) { alb = vec3f(0.015, 0.02, 0.03); } // NCX interceptor hull — near-black steel
if (wh.y > 22.5 && wh.y < 23.5) { alb = vec3f(0.10, 0.045, 0.02); } // THE EMBER BALL — dark iron under the burn
// lantern (headlamp at the camera — vf-lightdark will move it to the player)
let ld = normalize(ro - pos);
let dist = length(ro - pos);
var sh = 1.0;
let shD = select(14.0, 6.0, warp > 1.5);
if (dist < 10.0) { sh = mod_w3_shadow(pos + n * 0.04, ld, dist, shD); } // PERF: a REAL branch (select still ran the march) + shorter shadow reach in the lair
let diff = max(dot(n, ld), 0.0);
let atten = 4.5 / (1.0 + 0.09 * dist * dist);
col = alb * (vec3f(0.05, 0.06, 0.09) + vec3f(1.0, 0.82, 0.55) * diff * atten * sh) * ao;
// ── THE LANTERN REVEAL-LIGHT (Galen's interaction/superimposition): the THROWN
// lantern is a bright cool point light. Dark hidden rooms (the ambusher lair
// behind the column) DEVELOP into view where its reality-light falls — the
// light + the room shows the room. A little ambient fill so back walls read too.
if (uni(128) > 0.5) {
let lanP = vec3f(uni(129), uni(130), uni(131));
let lw = lanP - pos;
let d2l = dot(lw, lw);
let diffL = max(dot(n, lw * inverseSqrt(max(d2l, 1e-4))), 0.0);
let lanR = max(uni(132), 0.5) + 1.5;
let inRange = smoothstep(lanR * lanR, 0.0, d2l);
col += alb * vec3f(0.62, 0.78, 1.35) * (0.3 + diffL) * (3.6 / (1.0 + 0.32 * d2l)) * inRange * ao;
}
// THE THIN PLACE glows with its own light — the weave IS the illumination, so
// it ignores the lantern falloff (mat 18 woven room · mat 19 dissolving wall).
if (wh.y > 17.5 && wh.y < 18.5) { col = alb * 1.6 + alb * 0.5; } // the weave IS the light
// ORB BOUNCE — walls/floor/columns pick up the orb's color (point light at
// orbP). As uni(20) charges, the whole room reddens; uni(21) flash bursts it.
let odw = orbP - pos;
let d2w = dot(odw, odw);
let diffOrb = max(dot(n, odw * inverseSqrt(max(d2w, 1e-4))), 0.0);
col += alb * orbHue * diffOrb * (2.2 / (1.0 + 0.25 * d2w)) * orbAmp * ao;
if (wh.y > 7.5 && wh.y < 8.5) { // warren altar-CAGE / reward orb — cool glow
col += vec3f(0.5, 1.1, 2.0) * (1.5 + 0.5 * sin(time * 2.0));
}
if (wh.y > 11.5 && wh.y < 12.5) { // THE KEY — gold, bright so it reads through the cage
col += vec3f(2.2, 1.5, 0.4) * (1.4 + 0.3 * sin(time * 3.0));
}
// warren rooms SELF-GLOW so BOTH read at once from the nave through the arch
// (they're far from the headlamp — the reveal is the whole point of the wing)
if (wh.y > 5.5 && wh.y < 6.5 && pos.z < -12.5) { // room shell tint-glow
col += select(vec3f(0.42, 0.18, 0.05), vec3f(0.06, 0.16, 0.46), warp < 0.0);
}
if (wh.y > 6.5 && wh.y < 7.5) { col += vec3f(0.55, 0.24, 0.06); } // Room A colonnade glow (warm)
if (wh.y > 8.5 && wh.y < 9.5) { // LAIR: dying ember veins, slow wrong pulse
let vein = pow(0.5 + 0.5 * sin(pos.x * 7.0 + pos.y * 3.0 + pos.z * 5.0 + time * 0.4), 6.0);
col += vec3f(0.30, 0.05, 0.02) * vein * (0.5 + 0.5 * sin(time * 0.7));
col += vec3f(0.9, 0.12, 0.04) * max(uni(47), 0.0) * 0.35; // ambush alert bleeds red into the air (LAIR)
}
if (((pos.z > 56.0 && pos.z < 68.0) || (pos.z > 75.0 && pos.z < 89.5) || (pos.z > 91.0 && pos.z < 103.5)) && abs(pos.x) < 7.4) { // LURKER DIM + DEN INTERIOR + TOMB — self-luminous
let breathe = 0.5 + 0.5 * sin(time * 0.9 + pos.x * 1.7 + pos.y * 2.3);
col += alb * 0.4 + vec3f(0.30, 0.16, 0.55) * breathe * 0.85; // violet breathing air
col += vec3f(0.9, 0.2, 0.1) * pow(0.5 + 0.5 * sin(pos.x * 9.0 + pos.z * 7.0 + time * 1.2), 10.0) * 0.5; // crawling red filaments
}
// EXIT DOORWAY glow deleted (Galen) — it opens into the dodeca arena now, no beacon
if (wh.y > 13.5 && wh.y < 14.5) { // OCTAGON ARENA — lit cool crystal walls
let fr = pow(1.0 - max(dot(n, -rd), 0.0), 3.0);
let up = clamp(n.y * 0.5 + 0.5, 0.0, 1.0);
col = vec3f(0.17, 0.21, 0.31) * (0.5 + 0.75 * up) + vec3f(0.4, 0.62, 0.98) * fr * 0.9 + vec3f(0.10, 0.13, 0.18);
}
// ── NCX · THE CITY CROSSING — interceptor trim by class + THE EMBER BALL ──
if (wh.y > 19.5 && wh.y < 22.5) {
let ncls = i32(wh.y - 19.5);
var trim = vec3f(2.0, 0.32, 0.16); // drifter — dull red
if (ncls == 1) { trim = vec3f(2.6, 0.12, 0.10); } // dodger — crimson
if (ncls == 2) { trim = vec3f(3.2, 0.55, 0.28); } // hunter — blazing
var bei = 0; var bd2 = 1.0e9; // nearest ship owns this pixel
for (var ei = 0; ei < 3; ei++) {
let e = vec3f(uni(91 + ei * 3), uni(92 + ei * 3), uni(93 + ei * 3));
let dd2 = dot(pos - e, pos - e);
if (uni(92 + ei * 3) > 0.01 && dd2 < bd2) { bd2 = dd2; bei = ei; }
}
let q = ncx_shipQ(pos, bei) / 0.62; // unscaled blade space
let flare = fract(uni(143 + bei * 3));
let rimS = pow(1.0 - abs(dot(n, rd)), 2.0);
col = vec3f(0.015, 0.02, 0.03) + trim * rimS * 0.65;
col += trim * exp(-length(q - vec3f(0.0, 0.0, 0.95)) * 3.2) * (1.8 + flare * 4.0); // engine
col += trim * smoothstep(0.06, 0.0, abs(abs(q.x) - 1.05)) * smoothstep(0.5, 0.1, abs(q.z - 0.3)) * 0.8; // running lights
let blink = smoothstep(0.92, 1.0, sin(time * 5.0 + f32(bei) * 2.3) * 0.5 + 0.5);
col += vec3f(2.0) * blink * exp(-length(q - vec3f(0.0, 0.62, 0.9)) * 6.0); // beacon
}
if (wh.y > 22.5 && wh.y < 23.5) { // THE EMBER BALL — molten, flash-white on impact
let bfl = clamp(uni(87), 0.0, 1.0);
let rimb = pow(1.0 - max(dot(n, -rd), 0.0), 2.0);
col = mix(vec3f(3.0, 1.7, 0.8), vec3f(7.0, 5.5, 4.0), bfl) * 0.35 + vec3f(1.2, 0.6, 0.25) * rimb;
col += vec3f(0.9, 0.35, 0.1) * (0.5 + 0.5 * sin(time * 7.0)) * 0.3;
}
if (wh.y > 4.5 && wh.y < 5.5) { // MANIFOLD orb — BOILING BLACK FLESH
let charge = clamp(uni(20), 0.0, 1.0);
// near-black wet skin: what you SEE is the light on it, not the surface
let skin = vec3f(0.012, 0.006, 0.008);
// heat burns from INSIDE the folds — crevices (low AO) glow like embers
// under cracked black crust; calm = a deep violet smoulder, charged = furnace
let crev = pow(1.0 - ao, 1.6);
let emberCalm = vec3f(0.28, 0.03, 0.20);
let emberHot = vec3f(2.6, 0.22, 0.05);
let boil = 0.75 + 0.25 * sin(time * 2.3 + pos.x * 3.1 + pos.y * 2.7); // the glow SEETHES
// wet gloss: tight specular off the headlamp + fresnel rim so it reads slick
let spec = pow(max(dot(reflect(rd, n), ld), 0.0), 28.0) * 0.9;
let rim = pow(clamp(1.0 + dot(n, rd), 0.0, 1.0), 3.0);
col = skin * (0.15 + diff * atten * 0.5)
+ mix(emberCalm, emberHot, charge) * crev * boil * (0.8 + 1.6 * charge)
+ vec3f(0.9, 0.85, 0.9) * spec
+ mix(vec3f(0.25, 0.04, 0.30), vec3f(1.8, 0.15, 0.05), charge) * rim * (0.35 + 0.9 * charge)
+ vec3f(1.0, 0.9, 0.8) * uni(21) * 2.5; // white-hot strike flash
}
// ── THE RISEN NAVE (mat 10 arcade stone · mat 11 gable/facade) ──
// The payoff after the lair: OPEN and GRAND, "regular VEILFIRE" luminous-dark.
// A high raking key light (sun through the open clerestory) on pale weathered
// limestone, a lifted COOL sky-bounce hemispheric ambient (the space breathes
// air, not tomb), WARM EMBER accents pooling low (carried fire / floor lanterns),
// and an in-branch aerial haze so the growing cathedral recedes into grandeur.
if (wh.y > 9.5 && wh.y < 11.5) {
let isGable = wh.y > 10.5; // mat 11 gable, mat 10 arcade
// pale weathered limestone — the gable a touch lighter & cooler (fresh-cut)
var stone = select(vec3f(0.50, 0.47, 0.42), vec3f(0.56, 0.55, 0.53), isGable);
let weather = 0.82 + 0.18 * fbm3d(pos * 0.6 + vec3f(0.0, pos.y * 0.4, 0.0), 2);
stone = stone * weather; // streaked, not dead-flat
// HIGH KEY — sun raking down the open avenue; gated soft shadow so bays fall dark
let kd = normalize(vec3f(0.26, 0.90, 0.34));
let ksh = mod_w3_shadow(pos + n * 0.05, kd, 26.0, 10.0);
let kdiff = max(dot(n, kd), 0.0);
let key = vec3f(1.35, 1.20, 0.98) * kdiff * (0.35 + 0.65 * ksh);
// COOL SKY-BOUNCE hemispheric ambient — LIFTED (open to sky), warm ground bounce
let upN = clamp(n.y * 0.5 + 0.5, 0.0, 1.0);
let ambient = mix(vec3f(0.16, 0.13, 0.11), vec3f(0.32, 0.40, 0.56), upN);
// WARM EMBER accents low on the walls (carried-fire glow, breathing)
let low = smoothstep(3.6, 0.0, pos.y);
let ember = vec3f(0.85, 0.34, 0.12) * low * (0.32 + 0.14 * sin(time * 1.3 + pos.z * 0.5));
col = stone * (ambient + key) * ao + ember * stone;
if (isGable) { // glint on the spire tracery
let gspec = pow(max(dot(reflect(rd, n), kd), 0.0), 20.0);
col = col + vec3f(1.1, 1.0, 0.85) * gspec * 0.6;
}
// AERIAL PERSPECTIVE — far bays recede into a pale luminous haze (awe/openness).
// Bounded so grandeur survives the warren-tuned vf_atmos pass below.
let hazeSky = vec3f(0.28, 0.36, 0.50);
let aer = clamp(1.0 - exp(-max(worldT - 7.0, 0.0) * 0.045), 0.0, 0.80);
col = mix(col, hazeSky, aer);
}
}
// DIMENSION WARP v4 — THE RISEN NAVE is woven from MICRO SHADER ANIMATIONS.
// One full-res march (the v3 double-march lagged); any pixel whose hit LANDS in
// the column room becomes a living cell: screen-cell seeded, breathing and
// hue-drifting at its own tempo, rare cells sparking. Occlusion-correct;
// creatures untouched (the population composites over this afterwards).
{
let dwPos = ro + rd * wh.x;
// ramp: whispers in from HALFWAY down the approach corridor (z -40) and is
// fully woven by the nave mouth (z -58); full inside; hard stop past the gable.
var dwAmt = smoothstep(40.0, 58.0, -dwPos.z) * step(dwPos.z, -40.0) * step(-95.0, dwPos.z);
if (abs(dwPos.x) > 8.5) { dwAmt = 0.0; }
// THE ARENA breathes the same weave (Galen: "bring the pixels through the
// dragon room") — the whole deep end is inside the other dimension now.
if (dwPos.z <= -95.0 && dwPos.z > -116.0 && abs(dwPos.x - 4.012) < 11.0) { dwAmt = 1.0; }
// THE LANTERN — reality projected (Galen): inside the crystal's cone the
// dimension-weave RESOLVES back to solid stone. u121 active · u122-124 origin ·
// u125-127 dir. This is the keystone verb made visible — light restores reality.
if (uni(121) > 0.5) {
let lo = vec3f(uni(122), uni(123), uni(124));
let ld = vec3f(uni(125), uni(126), uni(127));
let toP = dwPos - lo;
let dl = length(toP) + 0.001;
let cone = smoothstep(0.55, 0.82, dot(toP / dl, ld)) * smoothstep(20.0, 1.0, dl);
dwAmt = dwAmt * (1.0 - cone);
col = col + vec3f(0.42, 0.26, 0.72) * cone * 0.55; // VISIBLE reality-light wash on the surfaces it touches
}
if (wh.x < mDist * 0.98 && dwAmt > 0.003 && dwPos.y < 14.0) {
// WHAPAM GROUND SHOCK — an expanding ring torn across the floor at the
// impact point (u74 life 1→0 · u75/76 impact x/z), nave floor only
if (uni(117) > 0.01 && dwPos.y < 0.6) {
let irr = length(vec2f(dwPos.x - uni(118), dwPos.z - uni(119)));
let ring = smoothstep(0.34, 0.0, abs(irr - (1.0 - uni(117)) * 4.2)) * uni(117);
col = col + vec3f(1.5, 0.55, 0.12) * ring * 1.6;
}
let dwCell = floor((uv * 0.5 + vec2f(0.5)) * 56.0);
let dwSeed = fract(sin(dot(dwCell, vec2f(127.1, 311.7))) * 43758.5453);
let dwPh = time * (0.6 + dwSeed * 1.8) + dwSeed * 6.2831;
let dwWob = sin(dwPh) * 0.5 + 0.5;
var dwC = mix(col, vec3f(col.b, col.r, col.g), dwWob * 0.20 * dwAmt);
dwC = dwC * (1.0 - (0.14 - 0.26 * dwWob) * dwAmt);
if (dwSeed > 0.98) { dwC = dwC + vec3f(0.5, 0.35, 0.18) * pow(sin(dwPh * 3.0) * 0.5 + 0.5, 6.0) * dwAmt; }
col = dwC;
}
}
// ── the population, depth-composited in front of the world ──
// POPULATION DEPTH CEILING: if the world ray HIT a surface, cull entities behind
// it (real occlusion). If it MISSED (open air — the risen nave march caps at 16u
// but the hall is 40m), do NOT cull population at that phantom cap — let bolts/
// tracers render out to their cullD. (This was the real "shots vanish in pixelland".)
// miss-ray population ceiling, BANDED for cost: only the HORIZON band (rays
// that can actually see bolts flying down the hall) pays the long ceiling;
// sky/floor miss-rays keep the cheap 16u cutoff (nearT=60 everywhere was the
// column-room lag: every open-air pixel entity-tested the whole population).
// ── DEN VIEW-PORTAL (Galen): a window into THE LURKER DIMENSION floating at
// the top of the den's stair shaft — see the other side before you climb.
// Painted view with parallax (v1), gated on the reveal being out.
if (uni(128) > 0.5 && ro.z < -50.0) { // constrained to the hall (was: rendered from anywhere the crystal was out)
let ppC = vec3f(-6.5, 7.0, -72.0);
let ppT = s3_sph(ro, rd, ppC, 1.15);
if (ppT > 0.0 && ppT < worldT) {
let vp = ro + rd * ppT;
let o = vp - ppC;
let breathe2 = 0.5 + 0.5 * sin(time * 0.9 + o.x * 2.0 + o.y * 2.5);
var pcol = vec3f(0.24, 0.13, 0.45) * (0.7 + 0.6 * breathe2);
pcol += vec3f(0.9, 0.2, 0.1) * pow(0.5 + 0.5 * sin(o.x * 8.0 + o.y * 6.0 - time * 1.4), 9.0) * 0.6;
let colBand = smoothstep(0.34, 0.08, abs(o.x + rd.x * 0.8));
pcol = mix(pcol, vec3f(0.05, 0.04, 0.08), colBand * 0.8); // the column, continuing beyond
pcol += vec3f(0.85, 0.4, 0.15) * pow(0.5 + 0.5 * sin((o.y + rd.y) * 9.0 + time * 0.6), 14.0) * colBand * 1.2; // ember steps receding
pcol += vec3f(0.6, 0.4, 1.2) * smoothstep(0.16, 0.0, abs(length(o) - 1.15)) * 1.6; // portal rim
col = pcol;
worldT = ppT;
}
}
var nearT = select(select(16.0, 44.0, abs(rd.y) < 0.14), worldT, wh.x >= 0.0);
let cnt = popCount() / 2;
for (var i = 0; i < cnt; i = i + 1) {
let a = pop(i * 2); // x, y, z, kind
let b = pop(i * 2 + 1); // hp01, phase, yaw, flags
let kind = a.w;
if (kind < 0.5) { continue; } // empty slot
let ppos = a.xyz;
let isDemon = kind < 1.5; // kind 1 walkers/flyers
let isAmb = kind >= 1.5 && kind < 2.5; // kind 2 AMBUSHERS (vf_amb)
let isDragon = kind >= 9.5 && kind < 10.5; // kind 10 THE DRAGON (vf_dragon)
let isLurker = kind >= 12.5 && kind < 13.5;
let isCrux = kind >= 13.5 && kind < 14.5; // kind 14 FLAMING CRUCIFIX (crux module) // kind 13 THE VEIL LURKER
// BUG WAS HERE: sphere sat at the feet (y=0, r=1.4) → it clipped everything
// above y=1.4: the ribcage, head, horns. Center it at torso height + widen so
// the WHOLE body is inside the sphere test AND the local march below. The
// ambusher's bound must cover BOTH the tall folded sliver (y→2.2) and the
// wide low pounce (|x|→0.85, z→1.4) → centre (0,0.95,0) r 1.95.
let bc = ppos + vec3f(0.0, select(select(select(select(0.0, 0.95, isAmb), 1.05, isDemon), 3.0, isDragon), 1.1, isLurker), 0.0);
let br = select(select(select(select(select(select(select(0.18, 0.30, kind > 5.5), 0.45, kind > 7.5), 1.95, isAmb), 1.6, isDemon), 7.0, isDragon), 2.2, isLurker), 4.1, isCrux);
let sh = s3_sph(ro, rdF, bc, br);
let cullD = select(select(select(15.0, 40.0, inRisen), 42.0, isDragon), 46.0, isCrux); // bolts/tracers stay visible across the 40m risen nave (was 15u — shots vanished in pixelland)
let camInside = isDragon && length(ro - bc) < br;
if ((sh < 0.0 && !camInside) || sh > nearT || sh > cullD) { continue; }
if (isDemon) {
// DEMON — local sphere-march (rotate the ray into the demon's yaw frame)
let yaw = b.z;
let atkv = b.w; // attack blend 0..1 → dm-arms lunge + eye flare
// DAMAGE FREAKOUT (universal law): slot 4 carries hp01 + hurt01 overloaded;
// while hurt, the FORM ITSELF tears — per-screen-band jitter shreds the body
let hurtE = max(0.0, b.x - 1.0);
let cy = cos(-yaw); let sy = sin(-yaw);
let o0 = ro - ppos;
var lro = vec3f(cy * o0.x + sy * o0.z, o0.y, -sy * o0.x + cy * o0.z);
if (hurtE > 0.02) {
lro = lro + vec3f(sin(time * 47.0 + uv.y * 90.0), sin(time * 53.0 + uv.x * 40.0), sin(time * 43.0 + uv.x * 70.0)) * 0.10 * hurtE;
}
let lrd = vec3f(cy * rdF.x + sy * rdF.z, rdF.y, -sy * rdF.x + cy * rdF.z);
var t = max(sh, 0.02);
var hitT = -1.0;
// PERF: 30→20 steps, min step 0.004→0.01 — a grazing ray can no longer burn
// the whole budget crawling; the demon is 1.6u tall, 20 coarser steps cover it
for (var s = 0; s < 20; s = s + 1) {
let d = vf_demon_c(lro + lrd * t, b.y, atkv); // COARSE: no skin fbm in the march
if (d < 0.006) { hitT = t; break; }
t = t + max(d * 0.85, 0.01);
if (t > nearT || t > sh + 2.0 * br) { break; }
}
if (hitT > 0.0 && hitT < nearT) {
nearT = hitT;
let lp = lro + lrd * hitT;
let e = 0.012;
let ek = vec2f(1.0, -1.0) * e; // 4-tap tetrahedral normal (was 6-tap) — full skinned field for surface detail
let nn = normalize(
ek.xyy * vf_demon(lp + ek.xyy, b.y, atkv) +
ek.yyx * vf_demon(lp + ek.yyx, b.y, atkv) +
ek.yxy * vf_demon(lp + ek.yxy, b.y, atkv) +
ek.xxx * vf_demon(lp + ek.xxx, b.y, atkv));
let ll = normalize(lro - lp); // headlamp, in local frame
let dist = length(ro - (ro + rdF * hitT));
let diff = max(dot(nn, ll), 0.0);
let atten = 4.0 / (1.0 + 0.10 * dist * dist);
let base = vec3f(0.11, 0.05, 0.045); // charred flesh
let ember = vec3f(1.0, 0.32, 0.07) * pow(clamp(0.6 - nn.y, 0.0, 1.0), 2.0) * (0.7 + 0.5 * sin(time * 6.0 + ppos.x));
// EYES THAT FOLLOW THE PLAYER — put the player into the demon's local
// frame and aim a tight bright pupil within each socket toward them; a
// dim socket haze sits behind. Sockets: headC(0,1.64,0.20)+(±0.058,0.02,0.11).
let pw = vec3f(uni(1), uni(2), uni(3)) - ppos;
let plL = vec3f(cy * pw.x + sy * pw.z, pw.y, -sy * pw.x + cy * pw.z);
let socL = vec3f(-0.058, 1.66, 0.31);
let socR = vec3f( 0.058, 1.66, 0.31);
// GLOWING EYES with a DARK PUPIL THAT TRACKS THE PLAYER. Carve each socket
// to black, then paint a bright amber eyeball with a genuinely dark pupil
// hole that rides across it toward the player — a dark pupil on a bright
// eye reads as a gaze (that's how real eyes work).
// Work in the socket FACE PLANE (lateral x,y only) — the socket is a
// concave pit, so 3D distances fight the depth curvature. latL/latR are
// the pixel's offset across each eye's face; eyeball + tracking pupil are
// 2D discs in that plane, so the pupil rides cleanly toward the player.
// TRUE 3D EYEBALLS (was flat front-face decals that vanished in profile).
// The eyeball is a spherical region around each socket — a 3D distance,
// so the back of the skull is excluded for free (no front-gate needed) and
// the eye stays visible when the demon turns sideways. The pupil sits on
// the hemisphere pointing at the player in FULL 3D, so it tracks from any
// viewing angle — including when the head is turned away while strafing.
let R_eye = 0.078; let R_pup = 0.032;
let dEyeL = length(lp - socL); let dEyeR = length(lp - socR);
let eyeball = max(smoothstep(R_eye, R_eye * 0.45, dEyeL),
smoothstep(R_eye, R_eye * 0.45, dEyeR));
// pupil = a spot on each eyeball in the direction of the player (3D)
let pupCL = socL + normalize(plL - socL) * R_eye;
let pupCR = socR + normalize(plL - socR) * R_eye;
let pupil = max(smoothstep(R_pup, R_pup * 0.35, length(lp - pupCL)),
smoothstep(R_pup, R_pup * 0.35, length(lp - pupCR)));
let eyeFlare = 1.0 + 0.4 * sin(time * 5.0) + atkv * 1.6; // glare harder mid-attack
var flesh = base * (0.12 + diff * atten) + ember * 0.9;
// ORB BOUNCE on the demon — rotate the local normal back to world and
// light it from the orb, so demons redden with the charge like the room.
let wn = vec3f(cy * nn.x - sy * nn.z, nn.y, sy * nn.x + cy * nn.z);
let wp = ro + rdF * hitT;
let odm = orbP - wp;
let d2m = dot(odm, odm);
let diffM = max(dot(wn, odm * inverseSqrt(max(d2m, 1e-4))), 0.0);
flesh += orbHue * diffM * (2.2 / (1.0 + 0.25 * d2m)) * orbAmp * 0.55;
flesh = mix(flesh, vec3f(0.0), eyeball * 0.92); // socket carved dark
col = flesh + vec3f(2.8, 0.60, 0.06) * eyeball * (1.0 - pupil) * eyeFlare;
}
} else if (isLurker) {
// THE VEIL LURKER v2 (vf_lurker module) — void chitin crawler with a PALE
// BONE FACE: sunken 3D player-tracking eyes, a mandible that GAPES as it
// closes on you, ember maw. b.y=phase, b.z=yaw, b.w=hurt/ghost tint.
let yaw = b.z;
let cyr = cos(-yaw); let syr = sin(-yaw);
let o0 = ro - ppos;
var lro = vec3f(cyr * o0.x + syr * o0.z, o0.y, -syr * o0.x + cyr * o0.z);
let lrd = vec3f(cyr * rdF.x + syr * rdF.z, rdF.y, -syr * rdF.x + cyr * rdF.z);
// DAMAGE FREAKOUT (Galen) — same law as the demon; b.w>0.5 = hurt pulse (0.35 = ghost tint, ignore)
let hurtL = select(0.0, b.w, b.w > 0.5);
if (hurtL > 0.02) { lro = lro + vec3f(sin(time * 47.0 + uv.y * 90.0), sin(time * 53.0 + uv.x * 40.0), sin(time * 43.0 + uv.x * 70.0)) * 0.10 * hurtL; }
// player in the lurker's local frame — drives the gape and the pupils
let pwv = vec3f(uni(1), uni(2), uni(3)) - ppos;
let plL = vec3f(cyr * pwv.x + syr * pwv.z, pwv.y, -syr * pwv.x + cyr * pwv.z);
let gape = smoothstep(6.5, 1.3, length(pwv.xz)); // it OPENS ITS FACE as it closes
var t = max(sh, 0.02);
var hitT = -1.0;
for (var s = 0; s < 28; s = s + 1) {
let d = vfl_c(lro + lrd * t, b.y, gape);
if (d < 0.008) { hitT = t; break; }
t = t + max(d * 0.8, 0.012);
if (t > nearT || t > sh + 2.0 * br) { break; }
}
if (hitT > 0.0 && hitT < nearT) {
nearT = hitT;
let lp = lro + lrd * hitT;
let lmat = vfl_map(lp, b.y, gape).y;
let ek = vec2f(1.0, -1.0) * 0.014;
let nn = normalize(
ek.xyy * vfl_c(lp + ek.xyy, b.y, gape) + ek.yyx * vfl_c(lp + ek.yyx, b.y, gape) +
ek.yxy * vfl_c(lp + ek.yxy, b.y, gape) + ek.xxx * vfl_c(lp + ek.xxx, b.y, gape));
let ll = normalize(lro - lp);
let diff = max(dot(nn, ll), 0.0);
let hdc = vfl_head(b.y); // live head center — one truth w/ the SDF
var lkc = vec3f(0.0);
if (lmat > 0.5) {
// ── THE FACE — pale grave-bone, the one bright thing on a void body ──
var fc = vec3f(0.62, 0.575, 0.50) * (0.16 + diff * 0.85);
fc *= 0.86 + 0.14 * sin(lp.x * 31.0 + lp.y * 17.0) * sin(lp.y * 23.0 - lp.z * 19.0);
let socL = hdc + vec3f(-0.125, 0.075, 0.26);
let socR = hdc + vec3f( 0.125, 0.075, 0.26);
let dsl = length(lp - socL); let dsr = length(lp - socR);
let hollow = max(smoothstep(0.16, 0.045, dsl), smoothstep(0.16, 0.045, dsr));
fc = mix(fc, vec3f(0.045, 0.03, 0.05), hollow * 0.92); // bruise-dark socket pits
// 3D EYES w/ dark tracking pupils (demon technique) — sunken, ember-violet iris
let R_eye = 0.105; let R_pup = 0.048;
let eyeball = max(smoothstep(R_eye, R_eye * 0.4, dsl), smoothstep(R_eye, R_eye * 0.4, dsr));
let pupCL = socL + normalize(plL - socL) * R_eye;
let pupCR = socR + normalize(plL - socR) * R_eye;
let pupil = max(smoothstep(R_pup, R_pup * 0.35, length(lp - pupCL)),
smoothstep(R_pup, R_pup * 0.35, length(lp - pupCR)));
let flare = 1.0 + 0.35 * sin(time * 4.3) + gape * 1.8; // eyes blaze as the maw opens
fc += vec3f(1.9, 0.22, 1.15) * eyeball * (1.0 - pupil) * flare;
if (lmat > 2.5) { // mandible — darker jawbone
fc = mix(fc, vec3f(0.30, 0.26, 0.23) * (0.2 + diff * 0.7), 0.55);
}
// TEETH — pale ridges hanging from the upper mouth rim, shown as it opens
let tzone = smoothstep(0.24, 0.30, lp.z - hdc.z) * (1.0 - smoothstep(0.10, 0.16, abs(lp.y - (hdc.y - 0.02))));
fc += vec3f(0.85, 0.80, 0.68) * pow(0.5 + 0.5 * sin(lp.x * 46.0), 6.0) * tzone * (0.3 + gape * 0.9);
// EMBER MAW — heat spills from the cavity when the jaw hangs open
let maw = smoothstep(0.15, 0.0, length(lp - (hdc + vec3f(0.0, -0.10, 0.32)))) * (0.1 + gape * 1.2);
fc += vec3f(1.8, 0.32, 0.05) * maw * (0.75 + 0.25 * sin(time * 9.0));
lkc = fc;
} else {
// ── the BODY — void chitin (kept law: red vein pulse, pulsing heart) ──
lkc = vec3f(0.03, 0.024, 0.045) * (0.3 + diff * 0.8);
let vein = pow(0.5 + 0.5 * sin(lp.y * 14.0 + b.y * 3.0), 6.0);
lkc += vec3f(0.9, 0.07, 0.04) * vein * 0.9;
let heart = length(lp - vec3f(0.0, 1.75, 0.0));
lkc += vec3f(1.2, 0.08, 0.05) * exp(-heart * heart * 3.0) * (0.5 + 0.5 * sin(time * 5.2));
}
let rim = pow(clamp(1.0 + dot(nn, lrd), 0.0, 1.0), 2.2);
lkc += vec3f(0.55, 0.18, 1.0) * rim * select(1.5, 0.5, lmat > 0.5); // violet unreal rim (soft on the face)
lkc += vec3f(1.4, 1.4, 1.5) * clamp(b.w, 0.0, 1.0); // hurt flash / ghost tint
col = lkc;
}
} else if (isAmb) {
// AMBUSHER — local sphere-march vf_amb in the creature's yaw frame.
// burst (b.w) drives fold→pounce; shading is near-black chitin with thin
// ember seams (flare with burst) + ONE wide reflective eyeshine band across
// the sensory head. NO tracking eyes (that's the demon).
let yaw = b.z;
let brst = clamp(b.w, 0.0, 1.0);
let hurtE = max(0.0, b.x - 1.0); // damage freakout (same law as the demon)
let cyr = cos(-yaw); let syr = sin(-yaw);
let o0 = ro - ppos;
var lro = vec3f(cyr * o0.x + syr * o0.z, o0.y, -syr * o0.x + cyr * o0.z);
if (hurtE > 0.02) {
lro = lro + vec3f(sin(time * 47.0 + uv.y * 90.0), sin(time * 53.0 + uv.x * 40.0), sin(time * 43.0 + uv.x * 70.0)) * 0.10 * hurtE;
}
let lrd = vec3f(cyr * rdF.x + syr * rdF.z, rdF.y, -syr * rdF.x + cyr * rdF.z);
var t = max(sh, 0.02);
var hitT = -1.0;
// PERF: 40→24 steps, min step 0.004→0.012 — the ambushers live in the column
// room; at close range their sphere covers half the screen and every pixel
// paid 40 crawling evals even on a miss. This was the column room's spike.
for (var s = 0; s < 24; s = s + 1) {
let d = vf_amb_c(lro + lrd * t, b.y, brst); // COARSE: no hide gyroid/fbm in the march
if (d < 0.007) { hitT = t; break; }
t = t + max(d * 0.85, 0.012);
if (t > nearT || t > sh + 2.0 * br) { break; }
}
if (hitT > 0.0 && hitT < nearT) {
nearT = hitT;
let lp = lro + lrd * hitT;
let e = 0.012;
let ek = vec2f(1.0, -1.0) * e; // 4-tap tetrahedral normal (was 6-tap) — full hide field for surface detail
let nn = normalize(
ek.xyy * vf_amb(lp + ek.xyy, b.y, brst) +
ek.yyx * vf_amb(lp + ek.yyx, b.y, brst) +
ek.yxy * vf_amb(lp + ek.yxy, b.y, brst) +
ek.xxx * vf_amb(lp + ek.xxx, b.y, brst));
let ll = normalize(lro - lp); // lantern rides the camera
let dist = hitT;
let diff = max(dot(nn, ll), 0.0);
let atten = 4.0 / (1.0 + 0.10 * dist * dist);
// near-black chitin
var chit = vec3f(0.020, 0.019, 0.024) * (0.10 + diff * atten * 0.5);
// EMBER SEAMS — gyroid iso ridges between plates; flare with burst
let gy = sin(lp.x * 14.0) * cos(lp.y * 14.0)
+ sin(lp.y * 14.0) * cos(lp.z * 14.0)
+ sin(lp.z * 14.0) * cos(lp.x * 14.0);
let seam = pow(1.0 - clamp(abs(gy), 0.0, 1.0), 6.0);
let seamGlow = seam * (0.22 + brst * 2.2) * (0.6 + 0.4 * sin(time * 7.0 + ppos.x));
chit += vec3f(1.0, 0.30, 0.05) * seamGlow;
// ORB BOUNCE — redden with the charge like the room + demons
let wn = vec3f(cyr * nn.x - syr * nn.z, nn.y, syr * nn.x + cyr * nn.z);
let wp = ro + rdF * hitT;
let odm = orbP - wp;
let d2m = dot(odm, odm);
let diffM = max(dot(wn, odm * inverseSqrt(max(d2m, 1e-4))), 0.0);
chit += orbHue * diffM * (2.0 / (1.0 + 0.25 * d2m)) * orbAmp * 0.5;
// WIDE REFLECTIVE EYESHINE BAND across the sensory head. Mirrors the
// amb-head "band contract": tip = mix(fold,pounce, brst). The band is
// broad in x, thin vertically, and glints when the lantern grazes it.
let htip = mix(vec3f(0.0, 2.20, 0.0), vec3f(0.0, 0.55, 1.35), brst);
let hb = lp - htip;
let bandZone = smoothstep(0.30, 0.0, length(vec2f(hb.y, hb.z * 0.6)))
* smoothstep(0.34, 0.0, abs(hb.x));
let spec = pow(max(dot(nn, ll), 0.0), 22.0);
let eyeshine = bandZone * (0.30 + spec * 3.0);
chit += vec3f(1.4, 1.15, 0.75) * eyeshine * (1.0 + brst * 0.8);
chit = mix(chit, vec3f(1.7, 1.6, 1.5), hurtE * 0.6);
chit = mix(chit, vec3f(1.7, 1.6, 1.5), hurtE * 0.6);
col = chit;
}
} else if (isDragon) {
// GLITCH (uni(63)) — the projection destabilizes: occasional frame drops,
// POSE SNAPS (the rig teleports between phases), and horizontal BAND TEARS.
let dwG = clamp(uni(63), 0.0, 1.0);
let gfr = fract(sin(floor(time * 22.0) * 91.17) * 43758.5453);
if (dwG > 0.02 && gfr < dwG * 0.28) { continue; }
// DRAGON — the boss, marched in its own bounded room (5.8u sphere at torso).
// Full part-rig (head/trunk/wings/limbs) composed by vf_dragon_c (coarse, no
// skin) for the march and vf_dragon (fine, skinned) for the normal; region id
// from vf_dragon_mat drives the material. Local yaw frame (b.z). Drives:
// ph = b.y (seconds phase), chg = b.w (fire wind-up), flare = uni(52) (wing spread).
let yaw = b.z;
var ph = b.y;
if (dwG > 0.02 && gfr > 0.55) { ph = ph + floor(gfr * 7.0) * 1.7; } // pose snap
let chg = b.w;
let flare = uni(52);
let cy = cos(-yaw); let sy = sin(-yaw);
let o0 = ro - ppos;
var lro = vec3f(cy * o0.x + sy * o0.z, o0.y, -sy * o0.x + cy * o0.z);
if (dwG > 0.02) {
let gband = floor((uv.y * 0.5 + 0.5) * 14.0);
let gbr = fract(sin(gband * 37.7 + floor(time * 16.0) * 17.3) * 43758.5453);
if (gbr < dwG * 0.5) { lro = vec3f(lro.x + (gbr - 0.25) * dwG * 1.4, lro.y, lro.z); } // band tear
}
let lrd = vec3f(cy * rdF.x + sy * rdF.z, rdF.y, -sy * rdF.x + cy * rdF.z);
var t = max(sh, 0.02);
var hitT = -1.0;
// 34 coarse steps over the skinless union; step floor 0.014 keeps a grazing
// ray off the whole budget while still covering the 5.8u body.
for (var s = 0; s < 24; s = s + 1) {
let d = vf_dragon_c(lro + lrd * t, ph, chg, flare); // COARSE: no skin displacement in the march
if (d < 0.008) { hitT = t; break; }
t = t + max(d * 0.8, 0.014);
if (t > nearT || t > sh + 2.0 * br) { break; }
}
if (hitT > 0.0 && hitT < nearT) {
nearT = hitT;
let lp = lro + lrd * hitT;
let e = 0.02;
let ek = vec2f(1.0, -1.0) * e; // 4-tap tetrahedral (was 6-tap) — same detail, a third off the normal cost
let nn = normalize(
ek.xyy * vf_dragon(lp + ek.xyy, ph, chg, flare) +
ek.yyx * vf_dragon(lp + ek.yyx, ph, chg, flare) +
ek.yxy * vf_dragon(lp + ek.yxy, ph, chg, flare) +
ek.xxx * vf_dragon(lp + ek.xxx, ph, chg, flare));
let ll = normalize(vec3f(0.35, 0.85, -0.4)); // key from above-front
let diff = clamp(dot(nn, ll), 0.0, 1.0);
let rim = pow(1.0 - clamp(dot(nn, -lrd), 0.0, 1.0), 3.0); // silhouette ember rim
let crev = pow(clamp(0.62 - nn.y, 0.0, 1.0), 2.0); // downfacing folds → ember veins
let vein = mix(vec3f(0.20, 0.04, 0.01), vec3f(1.1, 0.28, 0.04), chg);
// REGION MATERIAL — 0 hide · 1 belly · 2 membrane · 3 bone · 4 throat
let region = i32(vf_dragon_mat(lp, ph, chg, flare) + 0.5);
var flesh = vec3f(0.0);
if (region == 4) {
// THE FIRE — the body's core, always burning; roars toward white with the charge
let fl = 0.85 + 0.15 * sin(time * 9.0 + lp.y * 7.0);
flesh = mix(vec3f(1.5, 0.55, 0.12), vec3f(3.0, 1.6, 0.55), chg) * fl;
} else if (region == 3) {
// bone (horns / teeth / claws) — pale, high diffuse, low ember
flesh = vec3f(0.55, 0.50, 0.42) * (0.35 + diff * 1.3)
+ vein * crev * 0.15;
} else if (region == 2) {
// GLASS BLADE — near-black sheet with a hard ember backlight; the wing
// fan reads by the light knifing between separated blades
flesh = vec3f(0.03, 0.02, 0.045) * (0.25 + diff * 0.6)
+ vec3f(1.1, 0.34, 0.10) * pow(1.0 - abs(dot(nn, lrd)), 2.4) * (0.6 + 0.7 * chg);
} else if (region == 1) {
// belly — pale horn plates, banded, weaker ember
flesh = vec3f(0.34, 0.26, 0.18) * (0.28 + diff * 1.1) * (0.8 + 0.2 * sin(lp.y * 9.0))
+ vec3f(0.9, 0.28, 0.06) * rim * 0.3
+ vein * crev * (0.2 + 0.4 * chg);
} else {
// OBSIDIAN PLATE (0) — near-black volcanic glass. Form comes from a hard
// key glint + the INNER FIRE: any face turned toward the chest furnace
// catches the core light, so the gaps and inner faces burn.
let coreL = vec3f(0.0, 2.35, -0.15);
let toCore = normalize(coreL - lp);
let inner = clamp(dot(nn, toCore), 0.0, 1.0);
let glint = pow(max(dot(reflect(lrd, nn), ll), 0.0), 42.0);
flesh = vec3f(0.018, 0.014, 0.024) * (0.25 + diff * 0.7)
+ vec3f(1.0, 0.97, 0.92) * glint * 0.65
+ vec3f(1.3, 0.42, 0.09) * pow(inner, 1.6) * (0.85 + chg * 0.9)
+ vec3f(0.9, 0.25, 0.06) * rim * 0.45;
}
// EYES — gold exp-glow at the two sockets, brightening with the wind-up
let eg = exp(-46.0 * min(length(lp - vfd_eye(0, ph)), length(lp - vfd_eye(1, ph))));
flesh += vec3f(1.8, 1.1, 0.35) * eg * (0.5 + chg * 1.0);
col = flesh * exp(-0.028 * hitT); // permafog distance fade (softened — a boss reads from range)
// HURT FLASH — uni(57) = flinch01 from vf-arena-dragon: the body itself
// blanches white on a hit and decays over ~0.4s (the tracer-point cloud
// spawned inside the body was depth-occluded by this very surface).
col = mix(col, vec3f(1.9, 1.85, 1.7), clamp(uni(57), 0.0, 1.0) * 0.85);
// glitch chroma split — channels tear apart while destabilized
if (dwG > 0.02) { col = mix(col, col.gbr, dwG * 0.55 * step(0.5, fract(time * 13.7))); }
}
} else if (isCrux) {
// GIANT FLAMING CRUCIFIX (kind 14) — body lives in the crux module (59KB cap)
let cr = crux_draw(ro, rdF, ppos, b, sh, br, nearT, time, uv);
if (cr.w > 0.0) { nearT = cr.w; col = cr.rgb; } else { col += cr.rgb; }
} else {
// PROJECTILE / DEATH-BIT / PICKUP — emissive blob at the sphere hit
if (sh < nearT) {
nearT = sh;
let life = clamp(b.x, 0.0, 1.0);
if (kind > 11.5) {
// PURPLE CRYSTAL (kind 12) — the lantern's CAST crystal: cool blue-violet
// core with a bright inner glow, matches the held lantern's crystal.
col = vec3f(0.55, 0.30, 1.30) * (1.0 + life * 1.4) + vec3f(0.85, 0.78, 1.38) * pow(life, 2.5) * 1.2;
}
else if (kind > 10.5) {
// SCATTER BLADE (kind 11) — the floating sword: a hard steel glint
// spinning as a 4-point star, cyan edge; flares white when it's cutting
// (b.y = hit flag). b.x = spin phase.
let hpt = ro + rdF * sh;
let hoff = hpt - ppos;
let spin = b.x * 6.2831853;
let d1 = vec2f(cos(spin), sin(spin));
let d2 = vec2f(-d1.y, d1.x);
let hv = vec2f(hoff.x + hoff.z * 0.6, hoff.y);
let bar = min(abs(dot(hv, d1)), abs(dot(hv, d2)));
let star = exp(-bar * bar * 220.0);
let steel = vec3f(1.5, 1.65, 1.9) * (0.5 + star * 1.6);
let edge = vec3f(0.35, 0.9, 1.6) * (1.0 - star) * 0.5;
col = (steel + edge) * (1.0 + b.y * 1.6);
}
else if (kind > 7.5) {
// PICKUPS — kind 8 ammo (amber), 9 health (green cross-cyan). A steady
// beacon that pulses + flickers a halo; life<1 (a timed drop) blinks faster as it fades.
let isHealth = kind > 8.5;
let base = select(vec3f(1.7, 1.05, 0.28), vec3f(0.35, 1.8, 0.55), isHealth);
let blink = select(1.0, 0.4 + 0.6 * step(0.5, fract(time * 2.5)), life < 0.999);
let pulse = 0.72 + 0.5 * sin(time * 4.0 + b.y);
col = base * pulse * blink * (0.85 + life * 0.6);
}
else if (kind > 6.5) { col = vec3f(1.9, 0.6, 0.12) * (0.5 + life * 1.6) + vec3f(1.9, 1.3, 0.7) * pow(life, 2.0) * 1.0; } // DRAGON FIREBALL — white-hot core cooling to orange
else if (kind > 5.5) { col = vec3f(1.7, 0.10, 0.95) * (0.6 + life * 2.2); } // ORB bolt — violet-red
else if (kind > 4.5) { col = vec3f(1.0, 0.42, 0.12) * (0.5 + life * 1.6); } // death ember
else { col = s3_tracerColor(kind, life) * 1.4; }
}
}
}
col = crux_halo(col, ro, rd, nearT, time); // crucifix firelight stains the streets
// ── VOLUMETRIC LIGHT — 13 stratified taps from ro toward the hit ──
// 1) lantern cone: dusty forward-scatter around where the player looks
// 2) god-rays: cool shafts back-projected along LDIR through the lancet
// (window rect centred (4.5, 2.2, 0), geometric mask only — no shadow taps)
// 3) orb air-scatter: the AIR itself reddens as uni(20) charges; uni(21) bursts
// Perf: 13 × (one 2-octave fbm3d + cheap math). No voronoi, no shadow march.
let vt = min(nearT, 14.0); // PERF: taps past the ~15u fog wall contribute nothing
let camFwd = normalize(uni4(61).xyz - ro);
let cone = smoothstep(0.55, 0.95, dot(rd, camFwd));
let LDIR = normalize(vec3f(-0.78, -0.35, 0.1));
let jit = hash31(vec3f(uv * 743.13, fract(time * 0.37))); // break banding
var volL = 0.0; // lantern inscatter
var volS = 0.0; // lancet shaft
var volO = 0.0; // orb scatter
for (var s = 0; s < 6; s = s + 1) { // PERF: 13→9→6 taps; jitter hides banding, permafog swallows the far end anyway
let tt = vt * (f32(s) + jit) / 6.0;
let sp = ro + rd * tt;
let dens = 0.5 + 0.5 * fbm3d(sp * 0.9 + vec3f(0.0, time * 0.06, 0.0), 2);
// lantern at the camera: density × distance-atten × look-cone
volL += dens * cone / (1.0 + 0.10 * tt * tt);
// god-rays: march the sample back along LDIR to the window plane x=4.5
let t2 = (sp.x - 4.5) / LDIR.x;
if (t2 > 0.0 && ro.z > -9.0) { // PERF: the lancet window only exists near the nave — skip god-ray math in the corridor/lair/risen nave
let q = sp - t2 * LDIR;
let dy = abs(q.y - 2.2) - 1.3;
let dz = abs(q.z) - 1.4;
volS += dens * smoothstep(0.9, 0.0, max(dy, dz)) * smoothstep(20.0, 2.0, t2);
}
// orb scatter: air glows the orb's hue near it (and everywhere on flash)
let dov = sp - orbP;
volO += dens / (1.0 + 0.35 * dot(dov, dov));
}
let vstep = vt / 6.0;
col += vec3f(1.0, 0.82, 0.55) * volL * vstep * 0.055; // warm dusty beam
col += vec3f(0.75, 0.85, 1.0) * 0.5 * volS * vstep * 0.45; // cool pale shafts
col += orbHue * volO * vstep * (0.10 + 0.32 * vfCharge + 0.9 * vfFlash); // the air turns
// ── atmosphere over whatever we hit, then flashes, then HUD ──
// vf_atmos fog is t*t (tuned for the tight warren — it swallows anything past
// ~15u). In the OPEN risen avenue that would flatten the 40u vista to cold murk,
// so feed it a bounded, saturating effective distance: keeps ember motes + tint
// and a gentle far haze while the grandeur (and the lifted sky) survive.
let atmosT = nearT; // PERMAFOG: the murk closes in EVERYWHERE — no grand-open exemption for the risen avenue (dread > awe)
col = vf_rulezones(col, ro + rd * nearT, time); // TIME CELLS: re-law the pixel by its world hit-point (before atmosphere)
col = vf_atmos(col, ro + rd * nearT, rd, atmosT, time);
col += vec3f(1.0, 0.82, 0.5) * max(uni(15), 0.0) * 0.6; // muzzle flash
col += vec3f(1.0, 0.22, 0.16) * max(uni(16), 0.0) * 0.5; // hit flash
let hud = s3_hud(uv, uni(6), uni(7), uni(8), uni(12));
col = mix(col, hud.rgb, hud.a);
return vec4f(col, 1.0);
}
// recompile nudge — binds the visual after a module-in-flight race (Aug 6)
visual · cflow
fn cf_segd(p: vec2f, a: vec2f, b: vec2f) -> f32 {
let pa = p - a; let ba = b - a;
let h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
return length(pa - ba * h);
}
fn cf_wcube(p: vec2f, c: vec2f, s: f32, yaw: f32, pit: f32) -> f32 {
var v = array<vec3f,8>(
vec3f(-1.0,-1.0,-1.0), vec3f(1.0,-1.0,-1.0), vec3f(1.0,1.0,-1.0), vec3f(-1.0,1.0,-1.0),
vec3f(-1.0,-1.0,1.0), vec3f(1.0,-1.0,1.0), vec3f(1.0,1.0,1.0), vec3f(-1.0,1.0,1.0));
let cy = cos(yaw); let sy = sin(yaw); let cp = cos(pit); let sp = sin(pit);
var pr = array<vec2f,8>();
for (var i = 0; i < 8; i = i + 1) {
var q = v[i];
let x = q.x * cy + q.z * sy; let z = -q.x * sy + q.z * cy; q = vec3f(x, q.y, z);
let yy = q.y * cp - q.z * sp; let z2 = q.y * sp + q.z * cp; q = vec3f(q.x, yy, z2);
pr[i] = c + s * vec2f(q.x, q.y);
}
var d = 1e9;
d = min(d, cf_segd(p, pr[0], pr[1])); d = min(d, cf_segd(p, pr[1], pr[2]));
d = min(d, cf_segd(p, pr[2], pr[3])); d = min(d, cf_segd(p, pr[3], pr[0]));
d = min(d, cf_segd(p, pr[4], pr[5])); d = min(d, cf_segd(p, pr[5], pr[6]));
d = min(d, cf_segd(p, pr[6], pr[7])); d = min(d, cf_segd(p, pr[7], pr[4]));
d = min(d, cf_segd(p, pr[0], pr[4])); d = min(d, cf_segd(p, pr[1], pr[5]));
d = min(d, cf_segd(p, pr[2], pr[6])); d = min(d, cf_segd(p, pr[3], pr[7]));
return d;
}
fn cf_bH(p: vec2f, c: vec2f, hw: f32, th: f32) -> f32 { let d = abs(p - c) - vec2f(hw, th); return min(max(d.x, d.y), 0.0) + length(max(d, vec2f(0.0))); }
fn cf_bV(p: vec2f, c: vec2f, hh: f32, th: f32) -> f32 { let d = abs(p - c) - vec2f(th, hh); return min(max(d.x, d.y), 0.0) + length(max(d, vec2f(0.0))); }
fn cf_digit(p: vec2f, c: vec2f, w: f32, h: f32, n: i32, th: f32) -> f32 {
var d = 1e9; let hl = h * 0.5 - th;
var A=false; var B=false; var C=false; var Dd=false; var E=false; var F=false; var G=false;
if (n==0){A=true;B=true;C=true;Dd=true;E=true;F=true;}
else if (n==1){B=true;C=true;}
else if (n==2){A=true;B=true;G=true;E=true;Dd=true;}
else if (n==3){A=true;B=true;G=true;C=true;Dd=true;}
else if (n==4){F=true;G=true;B=true;C=true;}
else {A=true;F=true;G=true;C=true;Dd=true;}
if(A){d=min(d,cf_bH(p,c+vec2f(0.0,-h),w,th));}
if(Dd){d=min(d,cf_bH(p,c+vec2f(0.0,h),w,th));}
if(G){d=min(d,cf_bH(p,c,w,th));}
if(F){d=min(d,cf_bV(p,c+vec2f(-w,-h*0.5),hl,th));}
if(B){d=min(d,cf_bV(p,c+vec2f(w,-h*0.5),hl,th));}
if(E){d=min(d,cf_bV(p,c+vec2f(-w,h*0.5),hl,th));}
if(C){d=min(d,cf_bV(p,c+vec2f(w,h*0.5),hl,th));}
return d;
}
// ── KANJI NUMERALS 一二三四五 (Galen: japanese number symbols for the weapon
// brackets) — built from the same horizontal/vertical bar primitives as the
// 7-seg digits, so slots 1-5 read as 一 二 三 四 五 inside their brackets. ──
fn cf_kanji(p: vec2f, c: vec2f, w: f32, h: f32, n: i32, th: f32) -> f32 {
var d = 1e9;
let kt = th * 0.58; // thinner than the 7-seg so bars keep gaps
let gh = h * 0.82; // glyph a touch shorter than the bracket
if (n == 1) { // 一
d = cf_bH(p, c, w, kt);
} else if (n == 2) { // 二
d = min(cf_bH(p, c + vec2f(0.0, -gh * 0.55), w * 0.74, kt),
cf_bH(p, c + vec2f(0.0, gh * 0.55), w, kt));
} else if (n == 3) { // 三
d = cf_bH(p, c + vec2f(0.0, -gh * 0.8), w * 0.7, kt);
d = min(d, cf_bH(p, c, w * 0.86, kt));
d = min(d, cf_bH(p, c + vec2f(0.0, gh * 0.8), w, kt));
} else if (n == 4) { // 四 — box + two inner legs
d = cf_bH(p, c + vec2f(0.0, -gh), w, kt);
d = min(d, cf_bH(p, c + vec2f(0.0, gh), w, kt));
d = min(d, cf_bV(p, c + vec2f(-w, 0.0), gh, kt));
d = min(d, cf_bV(p, c + vec2f( w, 0.0), gh, kt));
d = min(d, cf_bV(p, c + vec2f(-w * 0.36, gh * 0.30), gh * 0.42, kt));
d = min(d, cf_bV(p, c + vec2f( w * 0.36, gh * 0.30), gh * 0.42, kt));
} else { // 五
d = cf_bH(p, c + vec2f(0.0, -gh), w, kt);
d = min(d, cf_bH(p, c + vec2f(0.0, gh), w, kt));
d = min(d, cf_bV(p, c + vec2f(-w * 0.12, -gh * 0.06), gh * 0.94, kt));
d = min(d, cf_bH(p, c + vec2f( w * 0.18, gh * 0.18), w * 0.7, kt));
}
return d;
}
fn cf_frame(p: vec2f, c: vec2f, w: f32, h: f32, th: f32) -> f32 {
var d = 1e9;
d = min(d, cf_bV(p, c + vec2f(-w, 0.0), h, th)); d = min(d, cf_bV(p, c + vec2f(w, 0.0), h, th));
d = min(d, cf_bH(p, c + vec2f(-w + 0.012, -h), 0.012, th)); d = min(d, cf_bH(p, c + vec2f(w - 0.012, -h), 0.012, th));
d = min(d, cf_bH(p, c + vec2f(-w + 0.012, h), 0.012, th)); d = min(d, cf_bH(p, c + vec2f(w - 0.012, h), 0.012, th));
return d;
}
fn cf_crystal(p: vec2f, c: vec2f, s: f32, yaw: f32) -> f32 {
// 6 verts of an elongated octahedron (a gem): tall on y
var v = array<vec3f,6>(
vec3f(0.0, 1.7, 0.0), vec3f(0.0, -1.7, 0.0),
vec3f(0.85, 0.0, 0.0), vec3f(-0.85, 0.0, 0.0),
vec3f(0.0, 0.0, 0.85), vec3f(0.0, 0.0, -0.85));
let cy = cos(yaw); let sy = sin(yaw);
var pr = array<vec2f,6>();
for (var i = 0; i < 6; i = i + 1) {
var q = v[i];
let x = q.x * cy + q.z * sy; let z = -q.x * sy + q.z * cy;
pr[i] = c + s * vec2f(x, q.y);
}
var d = 1e9;
// top(0) to 4 ring(2,3,4,5)
d = min(d, cf_segd(p, pr[0], pr[2])); d = min(d, cf_segd(p, pr[0], pr[3]));
d = min(d, cf_segd(p, pr[0], pr[4])); d = min(d, cf_segd(p, pr[0], pr[5]));
// bottom(1) to ring
d = min(d, cf_segd(p, pr[1], pr[2])); d = min(d, cf_segd(p, pr[1], pr[3]));
d = min(d, cf_segd(p, pr[1], pr[4])); d = min(d, cf_segd(p, pr[1], pr[5]));
// ring 2-4-3-5-2
d = min(d, cf_segd(p, pr[2], pr[4])); d = min(d, cf_segd(p, pr[4], pr[3]));
d = min(d, cf_segd(p, pr[3], pr[5])); d = min(d, cf_segd(p, pr[5], pr[2]));
return d;
}
// ── 3D SDF helpers for the arcane lantern viewmodel (IQ) ──
fn cfl_boxframe(p: vec3f, b: vec3f, e: f32) -> f32 {
let q = abs(p) - b;
let w = abs(q + e) - e;
return min(min(
length(max(vec3f(q.x, w.y, w.z), vec3f(0.0))) + min(max(q.x, max(w.y, w.z)), 0.0),
length(max(vec3f(w.x, q.y, w.z), vec3f(0.0))) + min(max(w.x, max(q.y, w.z)), 0.0)),
length(max(vec3f(w.x, w.y, q.z), vec3f(0.0))) + min(max(w.x, max(w.y, q.z)), 0.0));
}
fn cfl_octa(p: vec3f, s: f32) -> f32 { let q = abs(p); return (q.x + q.y + q.z - s) * 0.57735; }
fn cfl_torus(p: vec3f, R: f32, r: f32) -> f32 { let q = vec2f(length(p.xz) - R, p.y); return length(q) - r; }
fn cfl_box(p: vec3f, b: vec3f) -> f32 { let q = abs(p) - b; return length(max(q, vec3f(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0); }
fn visual_cflow(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
let p = vec2f(uv.x * 0.5 + 0.5, 0.5 - uv.y * 0.5);
var col = vec3f(0.0); var a = 0.0;
// ── crystal->orb wireframe-cube flow ──
let inten = uni(100);
if (inten > 0.001) {
let src = vec2f(uni(101), uni(102)); let tgt = vec2f(uni(103), uni(104));
let phase = uni(105); let n = max(uni(106), 1.0); let dir = tgt - src;
var glow = 0.0;
for (var i = 0; i < 12; i = i + 1) {
if (f32(i) >= n) { break; }
let tt = fract(f32(i) / n + phase);
let c = src + dir * tt; let env = sin(tt * 3.14159265);
let s = 0.028 * (0.5 + 0.7 * env); let rot = time * 1.7 + f32(i) * 0.9;
glow = glow + smoothstep(0.006, 0.0, cf_wcube(p, c, s, rot, rot * 0.6 + 0.5)) * env;
}
glow = clamp(glow, 0.0, 1.0) * inten;
col = col + vec3f(0.40, 0.80, 1.0) * glow * 1.7; a = max(a, glow);
}
// ── VISUAL weapon brackets [1][2][3][4][5] — active = uni(69) ──
let wsel = i32(uni(69) + 0.5);
let owned = i32(uni(70) + 0.5); // ownership bitmask — a slot shows only once owned
let y = 0.92; let dw = 0.011; let dh = 0.018; let th = 0.006;
let fw = 0.028; let fh = 0.033;
for (var k = 0; k < 5; k = k + 1) {
if (((owned >> u32(k)) & 1) == 0) { continue; } // un-owned slot: hidden
let cx = 0.075 + f32(k) * 0.076;
let c = vec2f(cx, y);
let on = ((k + 1) == wsel);
let g = max(smoothstep(th, 0.0, cf_kanji(p, c, dw, dh, k + 1, th)),
smoothstep(th, 0.0, cf_frame(p, c, fw, fh, th * select(1.0, 1.6, on))));
let tint = select(vec3f(0.58, 0.64, 0.74), vec3f(0.45, 0.90, 1.0), on) * select(1.5, 2.6, on);
col = col + tint * g; a = max(a, g * select(0.92, 1.0, on));
}
// ── HELD ARCANE LANTERN (weapon 1, uni71) — a REAL 3D raymarched model: an iron
// cage (box-frame) holding a glowing crystal octahedron, a ring handle on top;
// sways + slowly spins. Bounded to the lower-left corner so it stays cheap. ──
let held = uni(71);
if (held > 0.001 && uv.x < 0.25 && uv.y > -0.05) {
let sway = vec3f(0.018 * sin(time * 1.05), 0.016 * sin(time * 1.5), 0.0);
let LG = vec3f(-0.40, -0.30, 0.60) + sway; // held lower-left, forward
let ro3 = vec3f(0.0, 0.0, 0.0);
let rd3 = normalize(vec3f(uv.x * 0.72, -uv.y * 0.72, 1.0));
let boc = ro3 - LG; let bqb = dot(boc, rd3);
if (bqb * bqb - (dot(boc, boc) - 0.055) >= 0.0) {
var t3 = 0.25; var hit = -1.0; var hmat = 0.0;
for (var i = 0; i < 24; i = i + 1) {
var g = ro3 + rd3 * t3 - LG;
let a = time * 0.6; let cq = cos(a); let sq = sin(a);
g = vec3f(g.x * cq + g.z * sq, g.y, -g.x * sq + g.z * cq); // slow spin
let cage = cfl_boxframe(g, vec3f(0.075, 0.10, 0.075), 0.010);
let capT = cfl_box(g - vec3f(0.0, 0.105, 0.0), vec3f(0.06, 0.012, 0.06));
let capB = cfl_box(g - vec3f(0.0, -0.105, 0.0), vec3f(0.07, 0.014, 0.07));
let handle = cfl_torus(g - vec3f(0.0, 0.16, 0.0), 0.03, 0.008);
let iron = min(min(cage, handle), min(capT, capB));
let core = cfl_octa(g, 0.075); // the crystal
var d = min(iron, core);
if (core < iron) { hmat = 1.0; } else { hmat = 0.0; }
if (d < 0.0025) { hit = t3; break; }
t3 = t3 + max(d * 0.8, 0.003);
if (t3 > 1.3) { break; }
}
if (hit > 0.0) {
var g = ro3 + rd3 * hit - LG;
let spn = time * 0.6; let cq = cos(spn); let sq = sin(spn);
g = vec3f(g.x * cq + g.z * sq, g.y, -g.x * sq + g.z * cq);
if (hmat > 0.5) {
if (uni(128) > 0.5) {
// the crystal has been CAST OUT — an empty, faintly-lit socket
col = vec3f(0.06, 0.05, 0.10);
} else {
// CRYSTAL CORE — glowing violet, pulsing
let pulse = 0.7 + 0.3 * sin(time * 2.6);
col = vec3f(0.55, 0.28, 1.15) * (1.4 + pulse);
col = col + vec3f(1.0, 0.9, 1.3) * pow(max(0.0, 0.075 - cfl_octa(g, 0.075)) / 0.075, 2.0);
}
} else {
// IRON CAGE — dark metal, arcane rim, lit by the core within
let toCore = normalize(vec3f(0.0) - g);
let inner = clamp(dot(normalize(g), -toCore), 0.0, 1.0);
col = vec3f(0.08, 0.075, 0.11) + vec3f(0.5, 0.3, 0.95) * inner * 0.6;
col = col + vec3f(0.8, 0.6, 1.1) * pow(max(0.0, g.y * 3.0 + 0.4), 2.0) * 0.2; // top glint
}
a = 1.0;
}
// halo around the whole lantern
col = col + vec3f(0.4, 0.25, 0.7) * held * 0.0;
}
}
// ── HELD GUN viewmodel (weapon 2, uni72) — a first-person barrel + body in the
// lower-right, dark gunmetal with a hot muzzle glow. Procedural, no texture. ──
let gun = uni(72);
// ── THE VEILFIRE CARBINE — 2D with FAKE PERSPECTIVE (zero marching: frame
// rate is law). The barrel tapers to an interior vanishing point, parts
// shrink and darken with depth, the muzzle sits small and far — reads as a
// gun aimed INTO the scene at pure HUD cost. uv is Y-DOWN (+1 = bottom).
if (gun > 0.001 && uv.x > 0.05 && uv.y > 0.1) {
let kick = clamp(uni(15), 0.0, 1.0);
let gbob = 0.010 * sin(time * 2.1) + 0.006 * sin(time * 3.6 + 1.2);
// anchor bottom-right; recoil pulls back-right and pitches the frame
let gc = vec2f(0.62 + kick * 0.03, 0.78 + gbob * 0.5 + kick * 0.02);
let sp2 = (uv * 0.5 + vec2f(0.5)); // screen coords 0..1, y down
let q = sp2 - gc;
let ga = -0.30 - kick * 0.10; // barrel runs UP-INTO-SCENE (points away, not sideways)
let ca = cos(ga); let sa = sin(ga);
var qr = vec2f(q.x * ca - q.y * sa, q.x * sa + q.y * ca);
// depth along the barrel: x+ = deeper. persp shrinks widths & lifts the line.
let dpt = clamp(qr.x / 0.30, 0.0, 1.0);
let persp = 1.0 / (1.0 + dpt * 1.9);
qr = vec2f(qr.x, qr.y + dpt * dpt * 0.012); // slight rise toward the vanish point
// parts (widths scaled by persp)
let recv = cf_bH(qr, vec2f(0.010, 0.000), 0.055, 0.030 * persp + 0.006);
let slide = cf_bH(qr, vec2f(0.060, -0.024 * persp), 0.115, 0.013 * persp + 0.003);
let barrl = cf_bH(qr, vec2f(0.190, -0.012 * persp), 0.075, 0.011 * persp + 0.002);
let ring = cf_bV(qr, vec2f(0.262, -0.012 * persp), 0.020 * persp + 0.004, 0.006);
let sightF= cf_bV(qr, vec2f(0.238, -0.036 * persp), 0.012 * persp + 0.002, 0.004);
let gq = vec2f(qr.x + (qr.y - 0.05) * 0.42, qr.y);
let grip = cf_bV(gq, vec2f(-0.028, 0.085), 0.062, 0.024);
let guard = abs(length(qr - vec2f(0.012, 0.052)) - 0.038) - 0.006;
var d = min(min(recv, slide), min(barrl, grip));
d = min(d, min(ring, min(sightF, guard)));
let gmask = smoothstep(0.005, 0.0, d) * gun;
// shading: near = lighter gunmetal, deep = darker (the depth cue); top rim
let metal = mix(vec3f(0.17, 0.18, 0.23), vec3f(0.05, 0.052, 0.068), dpt);
var gcol = metal * (1.0 - 0.35 * smoothstep(0.0, 0.03, qr.y)); // underside shadow
// panel cuts + grip grooves
let cut = min(abs(qr.x + 0.035) - 0.0016, abs(qr.x - 0.075) - 0.0016);
gcol = mix(gcol, gcol * 0.5, smoothstep(0.0025, 0.0, cut) * step(d, 0.0));
let grv = smoothstep(0.0022, 0.0, abs(fract((gq.y - 0.03) * 34.0) - 0.5) * 0.028) * step(grip, 0.0);
gcol = mix(gcol, gcol * 0.55, grv);
col = mix(col, gcol, gmask);
// EMBER SEAM along the slide, fading with depth; top rim light
let seam = cf_bH(qr, vec2f(0.075, -0.010 * persp), 0.10, 0.0015);
col = col + vec3f(1.25, 0.36, 0.09) * smoothstep(0.004, 0.0, seam) * (1.0 - dpt * 0.6) * (0.6 + 0.4 * sin(time * 5.0 + qr.x * 40.0)) * gun;
col = col + vec3f(0.55, 0.66, 0.88) * smoothstep(0.012, 0.004, d) * smoothstep(0.005, -0.02, qr.y) * 0.35 * gun;
// muzzle — small and FAR: breathing ember; flash + star on fire
let mz = qr - vec2f(0.268, -0.012 * persp);
let heat = exp(-dot(mz, mz) * 5200.0);
col = col + vec3f(1.1, 0.5, 0.15) * heat * gun * (0.25 + 0.25 * sin(time * 9.0));
col = col + vec3f(1.5, 1.05, 0.55) * heat * kick * 3.0;
let star = max(0.0, 1.0 - abs(mz.x * 30.0)) * max(0.0, 1.0 - abs(mz.y * 130.0)) + max(0.0, 1.0 - abs(mz.y * 30.0)) * max(0.0, 1.0 - abs(mz.x * 130.0));
col = col + vec3f(1.5, 1.15, 0.6) * star * kick * 1.4;
a = max(a, max(gmask, heat * (0.3 + kick)));
}
// -- HELD EMBER BLADE viewmodel (weapon 4) -- gated on the AUTHORITATIVE active
// weapon uni(69)==4 (robust to slot ordering). 2D fake-persp sword, lower-right,
// dark steel + hot ember fuller. Zero marching (frame rate is law).
let bld = select(0.0, 1.0, uni(69) > 3.5 && uni(69) < 4.5);
if (bld > 0.001) {
let sp2 = (uv * 0.5 + vec2f(0.5));
let bbob = 0.010 * sin(time * 1.8) + 0.005 * sin(time * 3.1 + 0.7);
let swing = clamp(uni(15), 0.0, 1.0);
let bcp = vec2f(0.70 - swing * 0.06, 0.86 + bbob);
let q = sp2 - bcp;
let ba = -0.95 - swing * 0.5;
let ca = cos(ba); let sa = sin(ba);
let qr = vec2f(q.x * ca - q.y * sa, q.x * sa + q.y * ca);
let tdep = clamp(-qr.y / 0.42, 0.0, 1.0);
let persp = 1.0 / (1.0 + tdep * 1.4);
let grip = cf_bV(qr, vec2f(0.0, 0.048), 0.046, 0.013);
let pommel= length(qr - vec2f(0.0, 0.100)) - 0.018;
let guard = cf_bH(qr, vec2f(0.0, 0.0), 0.066, 0.011);
let blade = cf_bV(qr, vec2f(0.0, -0.205), 0.205, 0.017 * persp + 0.004);
let d = min(min(grip, pommel), min(guard, blade));
let bmask = smoothstep(0.006, 0.0, d) * bld;
let steel = mix(vec3f(0.42, 0.45, 0.52), vec3f(0.13, 0.14, 0.19), tdep);
var bcol = steel;
bcol = mix(bcol, vec3f(0.32, 0.22, 0.09), smoothstep(0.001, 0.0, min(guard, min(grip, pommel))));
col = mix(col, bcol, bmask);
let fuller = cf_bV(qr, vec2f(0.0, -0.205), 0.19, 0.0028);
col = col + vec3f(1.5, 0.44, 0.11) * smoothstep(0.004, 0.0, fuller) * (1.0 - tdep * 0.4) * (0.6 + 0.4 * sin(time * 5.0 + qr.y * 26.0)) * bld;
col = col + vec3f(0.62, 0.70, 0.88) * smoothstep(0.011, 0.003, d) * 0.4 * bld;
let tip = length(qr - vec2f(0.0, -0.40)) - 0.03;
col = col + vec3f(1.6, 1.15, 0.65) * smoothstep(0.05, 0.0, tip) * swing * 1.6;
a = max(a, bmask);
}
// -- HELD EMBER BALL viewmodel (weapon 3) -- gated on uni(69)==3. A molten glowing
// sphere cupped in the lower-right with fire flicker; fills the hand when you hold
// slot 3 (the city pinball still draws the real ball in world space when thrown).
let bl3 = select(0.0, 1.0, uni(69) > 2.5 && uni(69) < 3.5);
if (bl3 > 0.001) {
let sp2 = (uv * 0.5 + vec2f(0.5));
let b3bob = 0.012 * sin(time * 1.6) + 0.006 * sin(time * 2.9 + 0.5);
let ctr = vec2f(0.72, 0.85 + b3bob);
let rr = length(sp2 - ctr);
let R = 0.090;
if (rr < R * 1.7) {
let flick = 0.86 + 0.10 * sin(time * 9.0) + 0.05 * sin(time * 23.7);
let core = smoothstep(R, R * 0.25, rr);
let glow = exp(-pow(rr / (R * 1.35), 2.0) * 2.0);
let hot = smoothstep(R * 0.5, 0.0, rr);
let bcol = (vec3f(1.5, 0.55, 0.14) * core + vec3f(1.95, 1.25, 0.6) * hot) * flick;
col = mix(col, bcol, clamp(core * bl3, 0.0, 1.0));
col = col + vec3f(1.4, 0.5, 0.16) * glow * bl3 * 0.6;
a = max(a, clamp(core * bl3 + glow * bl3 * 0.4, 0.0, 1.0));
}
}
return vec4f(col, clamp(a, 0.0, 1.0));
}
visual · vf_ixpres
fn visual_vf_ixpres(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f { if (max(abs(uv.x), abs(uv.y)) > 0.98) { return vec4f(0.0); } return vec4f(color.rgb, 0.45); }visual · wbar
fn visual_wbar(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f { return vec4f(0.0); }visual · vf_embers
fn visual_vf_embers(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
// FLOATING EMBERS — a self-contained atmospheric overlay (own field, own
// visual; touches no shared uniform, clobbers nothing). Warm motes rise,
// sway, flicker, and burn out — ash-light drifting through the veil.
var glow = 0.0;
var em = vec3f(0.0);
for (var i = 0; i < 18; i = i + 1) {
let fi = f32(i);
let s1 = fract(sin(fi * 91.73) * 4321.13);
let s2 = fract(sin(fi * 47.11) * 1234.57);
let speed = 0.10 + s1 * 0.16;
let rise = fract(s2 + time * speed);
let sway = sin(time * (0.5 + s1) + fi * 1.7) * 0.09;
let px = (s1 * 2.0 - 1.0) + sway;
let py = rise * 2.2 - 1.1;
let d = length(uv - vec2f(px, py));
let size = 0.004 + s2 * 0.010;
let flick = 0.55 + 0.45 * sin(time * (2.5 + s1 * 5.0) + fi * 2.3);
let g = (size * size) / (d * d + size * size) * flick;
let life = smoothstep(0.0, 0.12, rise) * (1.0 - smoothstep(0.75, 1.0, rise));
glow += g * life;
em += mix(vec3f(1.7, 0.55, 0.12), vec3f(1.9, 0.95, 0.45), s2) * g * life;
}
let a = clamp(glow * 0.7, 0.0, 0.82);
return vec4f(em, a);
}
— SHADER MODULES —
module · cath
// VEILFIRE · cathedral.wgsl — THE RISEN NAVE (node: grown-cathedral).
// GENERATED by swarm/grow-cathedral.mjs (seed 1337). DO NOT hand-edit — regrow.
// EXPORT: fn mod_cath(p: vec3f) -> f32 (signed distance; true lower-bound early-out)
//
// A grown Gothic AVENUE: a pointed-arch COLONNADE down each side + a crow-step
// GABLE facade (blind arcature, lancets, spire) closing the far end. LIVE GROWTH
// via uni(48) (0→1) with per-bay cellStagger 0.6 — the street builds itself.
//
// ── PLOT / LINE CONTRACT (world units; rooms.wgsl + warren-extents.json MATCH) ──
// avenue runs −z: near end (opens) z=-56 → far gable plane z=-96 (len 40u)
// arcades at world |x| = 6 · air region |x| ≤ 8.5 · ceiling y ≤ 14
// arcade wallTop ≈ 9.37u · pinnacle top ≈ 10.92u · bays 10 @ 4.00u
// gable: 4 crow-steps, top ≈ 9.28u, spire top ≈ 11.88u
// avenue-local map: zl = -56 - p.z (0 near .. 40 far); right arcade xl=+p.x, left xl=-p.x
//
// COMPILE SAFETY: only mod_cath_* helpers here; prims (mod_w3_taperStrut/bezStrut/
// box, opSmoothUnion) + uni() come from the shared prelude — never redefined.
// ── LIVE GROWTH REVEAL (Jul 28 — hand-tuned over the generated baseline) ──────
// Each bay's growth is driven by the PLAYER's PROXIMITY, not a single global
// uni(48) clock. A bay begins to stir when the player is within CATH_REVEAL_R
// avenue-units (≈ world-units) and is fully risen ~CATH_REVEAL_FADE closer. Two
// wins in one edit:
// (1) under the dark permafog the rising columns are always right ahead of the
// player, INSIDE the visible bubble — you watch them build in the murk;
// (2) bays beyond the reveal radius stay ungrown, so their per-step struts stay
// gated OFF (`if (gw > 0.001)` fails) — the march cost no longer climbs as
// the avenue fills in behind you. Presence-built: walk away and it recedes.
// Activation is the DOOR latch uni(45) (0→1, stays 1), NOT the old penetration
// clock uni(48) — near the mouth uni(48)≈0, which would have left the first bays
// dead under the player's feet. NB: swarm/grow-cathedral.mjs still emits the old
// global clock — re-apply this reveal model there if the cathedral is regrown.
const CATH_REVEAL_R: f32 = 13.0; // avenue-u at which a bay begins to rise
const CATH_REVEAL_FADE: f32 = 9.0; // rises to full over this much closer approach
fn mod_cath_grow(zc: f32, pZL: f32) -> f32 {
let act = clamp(uni(45) * 2.0, 0.0, 1.0); // avenue 'active' once the corridor door opens
return clamp((CATH_REVEAL_R - abs(zc - pZL)) / CATH_REVEAL_FADE, 0.0, 1.0) * act;
}
// 2D pointed-arch opening (two-circle construction, open-bottom)
fn mod_cath_arc_op(z: f32, y: f32, hw: f32, sy: f32, k: f32, R: f32) -> f32 {
let ov = 0.1 * sqrt(max(R * R - k * k, 0.0));
let rect = max(abs(z) - hw, y - sy - ov);
let dy = y - sy;
let head = max(sy - y, max(length(vec2f(z + k, dy)) - R, length(vec2f(z - k, dy)) - R));
return min(rect, head);
}
fn mod_cath_arc(p: vec3f) -> f32 {
let gqb = abs(p - vec3f(6.0, 5.771, 20.0)) - vec3f(2.9389, 7.771, 21.5);
let gdb = length(max(gqb, vec3f(0.0)));
if (gdb > 0.5) { return gdb; }
let pZL = -56.0 - uni(3); // player's avenue-local z — the reveal distance axis
var d = 1e5;
{
let ci = clamp(floor((p.z - 0.0) / 4.0), 0.0, 9.0);
let zb = 0.0 + ci * 4.0;
let gbc = mod_cath_grow(zb + 2.0, pZL) * 9.0;
var s = 1e5;
let gw1 = clamp(gbc - 0.0, 0.0, 1.0); if (gw1 > 0.001) { s = min(s, mod_w3_box(p - vec3f(6.0, 4.6842, zb + 2.0), (vec3f(0.2435, 4.6842, 2.005) * gw1))); }
let gw2 = clamp(gbc - 1.0, 0.0, 1.0); if (gw2 > 0.001) { let go1 = mod_cath_arc_op(p.z - (zb + 2.0), p.y, 1.5125, 5.3725, 1.4459, 2.9584); let gf1 = max(abs(p.x - 6.0) - 0.2435, 0.0); s = opSmoothUnion(s, max(sqrt(go1 * go1 + gf1 * gf1) - (0.1877 * gw2), -p.y - 0.05), 0.0529); }
let gw3 = clamp(gbc - 2.0, 0.0, 1.0); if (gw3 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8782, 0.0, zb + 0.0), vec3f(5.8782, 5.3725, zb + 0.0), (0.3549 * gw3), (0.2998 * gw3))); }
let gw4 = clamp(gbc - 3.0, 0.0, 1.0); if (gw4 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8782, 0.0, zb + 4.0), vec3f(5.8782, 5.3725, zb + 4.0), (0.3549 * gw4), (0.2998 * gw4))); }
let gw5 = clamp(gbc - 4.0, 0.0, 1.0); if (gw5 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8539, 9.3683, zb + 0.0), vec3f(5.8539, 9.3683, zb + 4.0), (0.1978 * gw5), (0.1978 * gw5))); }
let zf = (p.z - 0.0) / 4.0 - ci;
let cn = select(ci + 1.0, ci - 1.0, zf < 0.5);
let zn = 0.0 + cn * 4.0;
let gbn = mod_cath_grow(zn + 2.0, pZL) * 9.0;
let nOk = cn >= 0.0 && cn <= 9.0;
if (nOk) {
let gw6 = clamp(gbn - 0.0, 0.0, 1.0); if (gw6 > 0.001) { s = min(s, mod_w3_box(p - vec3f(6.0, 4.6842, zn + 2.0), (vec3f(0.2435, 4.6842, 2.005) * gw6))); }
let gw7 = clamp(gbn - 1.0, 0.0, 1.0); if (gw7 > 0.001) { let go2 = mod_cath_arc_op(p.z - (zn + 2.0), p.y, 1.5125, 5.3725, 1.4459, 2.9584); let gf2 = max(abs(p.x - 6.0) - 0.2435, 0.0); s = opSmoothUnion(s, max(sqrt(go2 * go2 + gf2 * gf2) - (0.1877 * gw7), -p.y - 0.05), 0.0529); }
let gw8 = clamp(gbn - 2.0, 0.0, 1.0); if (gw8 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8782, 0.0, zn + 0.0), vec3f(5.8782, 5.3725, zn + 0.0), (0.3549 * gw8), (0.2998 * gw8))); }
let gw9 = clamp(gbn - 3.0, 0.0, 1.0); if (gw9 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8782, 0.0, zn + 4.0), vec3f(5.8782, 5.3725, zn + 4.0), (0.3549 * gw9), (0.2998 * gw9))); }
let gw10 = clamp(gbn - 4.0, 0.0, 1.0); if (gw10 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(5.8539, 9.3683, zn + 0.0), vec3f(5.8539, 9.3683, zn + 4.0), (0.1978 * gw10), (0.1978 * gw10))); }
}
for (var bo: i32 = -1; bo <= 2; bo++) {
let bi = i32(ci) + bo;
if (bi < 0 || bi > 10 || bi % 2 != 0) { continue; }
let zr = 0.0 + f32(bi) * 4.0;
let gbr = mod_cath_grow(zr + 0.0, pZL) * 9.0;
let gw11 = clamp(gbr - 5.0, 0.0, 1.0); if (gw11 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(6.8251, 0.0, zr + 0.0), vec3f(6.8251, 8.2441, zr + 0.0), (0.5323 * gw11), (0.3189 * gw11))); }
let gw12 = clamp(gbr - 6.0, 0.0, 1.0); if (gw12 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(6.8251, 8.2441, zr + 0.0), vec3f(6.0, 9.0873, zr + 0.0), (0.287 * gw12), (0.1754 * gw12))); }
let gw13 = clamp(gbr - 7.0, 0.0, 1.0); if (gw13 > 0.001) { s = min(s, mod_w3_taperStrut(p, vec3f(6.8251, 8.2441, zr + 0.0), vec3f(6.8251, 10.9179, zr + 0.0), (0.2392 * gw13), (0.02 * gw13))); }
}
s = max(s, -max(mod_cath_arc_op(p.z - (zb + 2.0), p.y, 1.5125, 5.3725, 1.4459, 2.9584), abs(p.x - 6.0) - 0.6435));
if (nOk) {
s = max(s, -max(mod_cath_arc_op(p.z - (zn + 2.0), p.y, 1.5125, 5.3725, 1.4459, 2.9584), abs(p.x - 6.0) - 0.6435));
}
d = min(d, s);
}
return d;
}
// 2D pointed-arch opening (two-circle construction, open-bottom)
fn mod_cath_gab_op(z: f32, y: f32, hw: f32, sy: f32, k: f32, R: f32) -> f32 {
let ov = 0.1 * sqrt(max(R * R - k * k, 0.0));
let rect = max(abs(z) - hw, y - sy - ov);
let dy = y - sy;
let head = max(sy - y, max(length(vec2f(z + k, dy)) - R, length(vec2f(z - k, dy)) - R));
return min(rect, head);
}
fn mod_cath_gab(p: vec3f) -> f32 {
let gqb = abs(p - vec3f(0.0, 5.7643, 40.0)) - vec3f(8.5, 7.7643, 2.4367);
let gdb = length(max(gqb, vec3f(0.0)));
if (gdb > 0.5) { return gdb; }
let pZL = -56.0 - uni(3); // player's avenue-local z — the reveal distance axis
let gbf = mod_cath_grow(40.0, pZL) * 12.0; // gable rises only as you near the far end
var d = 1e5;
let gw1 = clamp(gbf - 0.0, 0.0, 1.0); if (gw1 > 0.001) { d = min(d, mod_w3_box(p - vec3f(0.0, 1.7099, 40.0), (vec3f(7.5, 1.7099, 0.7183) * gw1))); }
let gw2 = clamp(gbf - 1.0, 0.0, 1.0); if (gw2 > 0.001) { d = min(d, mod_w3_box(p - vec3f(0.0, 2.8165, 40.0), (vec3f(5.3981, 2.8165, 0.7183) * gw2))); }
let gw3 = clamp(gbf - 2.0, 0.0, 1.0); if (gw3 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(6.449, 1.8809, 40.0), vec3f(6.449, 3.4198, 40.0), (0.42 * gw3), (0.36 * gw3))); }
let gw4 = clamp(gbf - 3.0, 0.0, 1.0); if (gw4 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(6.449, 3.4198, 40.0), vec3f(6.449, 6.0353, 40.0), (0.36 * gw4), (0.02 * gw4))); }
let gw5 = clamp(gbf - 4.0, 0.0, 1.0); if (gw5 > 0.001) { d = min(d, mod_w3_box(p - vec3f(0.0, 3.7713, 40.0), (vec3f(3.3958, 3.7713, 0.7183) * gw5))); }
let gw6 = clamp(gbf - 5.0, 0.0, 1.0); if (gw6 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(4.3969, 3.0981, 40.0), vec3f(4.3969, 5.633, 40.0), (0.42 * gw6), (0.36 * gw6))); }
let gw7 = clamp(gbf - 6.0, 0.0, 1.0); if (gw7 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(4.3969, 5.633, 40.0), vec3f(4.3969, 8.0543, 40.0), (0.36 * gw7), (0.02 * gw7))); }
let gw8 = clamp(gbf - 7.0, 0.0, 1.0); if (gw8 > 0.001) { d = min(d, mod_w3_box(p - vec3f(0.0, 4.6393, 40.0), (vec3f(1.5375, 4.6393, 0.7183) * gw8))); }
let gw9 = clamp(gbf - 8.0, 0.0, 1.0); if (gw9 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(2.4666, 4.1484, 40.0), vec3f(2.4666, 7.5426, 40.0), (0.42 * gw9), (0.36 * gw9))); }
let gw10 = clamp(gbf - 9.0, 0.0, 1.0); if (gw10 > 0.001) { d = min(d, mod_w3_taperStrut(vec3f(abs(p.x), p.y, p.z), vec3f(2.4666, 7.5426, 40.0), vec3f(2.4666, 9.8529, 40.0), (0.36 * gw10), (0.02 * gw10))); }
let gw11 = clamp(gbf - 10.0, 0.0, 1.0); if (gw11 > 0.001) { d = min(d, mod_w3_taperStrut(p, vec3f(0.0, 8.8785, 40.0), vec3f(0.0, 12.5285, 40.0), (0.6457 * gw11), (0.02 * gw11))); }
d = max(d, -max(mod_cath_gab_op(abs(p.x) - 4.125, p.y, 1.05, 1.4363, 1.176, 2.226), abs(p.z - 39.2817) - 0.16));
d = max(d, -max(mod_cath_gab_op(abs(p.x) - 2.699, p.y, 0.8637, 4.3493, 0.9673, 1.831), abs(p.z - 39.2817) - 0.16));
d = max(d, -max(max(mod_cath_gab_op(p.x - 0.0, p.y, 0.7471, 6.6833, 0.8367, 1.5838), 5.9194 - p.y), abs(p.z - 40.0) - 1.4367));
return d;
}
// ── WEAVE-THROUGH FOREST (Jul 28) — a hypostyle grid of piers grown into the
// avenue floor, STAGGERED row-to-row so there is ALWAYS a slalom gap (never
// sealed). Each pier rises by the player's PROXIMITY — it grows toward you as you
// advance and sinks behind — so the building crowds you but always lets you pass.
// Operates in WORLD space (uni(3)=player world z is the reveal axis). Collision is
// mirrored in movement.mjs cathGroveBlocked (same grid); the full-grown reveal
// (~4u out) always precedes collision contact (~0.9u) so you never hit an unseen
// pier. Architectural (fluteless stone shafts + capitals) — this is the calm room.
const CATH_GROVE_R: f32 = 9.0; // a pier begins to rise within this of the player
const CATH_GROVE_FADE: f32 = 5.0; // fully risen this much closer (> collision reach → seen first)
const CATH_GROVE_H: f32 = 9.2; // full pier height (floor → vault springing)
const CATH_GROVE_Z0: f32 = -62.0; // first row (leaves an ~8u clearing at the avenue mouth)
fn mod_cath_pier(p: vec3f, cx: f32, cz: f32, H: f32) -> f32 {
let q = vec3f(p.x - cx, p.y, p.z - cz);
let rad = 0.40 + 0.06 * cos(clamp(q.y / H, 0.0, 1.0) * 3.14159); // slight entasis (fuller low, tapers up)
let shaft = max(length(vec2f(q.x, q.z)) - rad, abs(q.y - H * 0.5) - H * 0.5);
let cap = mod_w3_box(vec3f(q.x, q.y - H, q.z), vec3f(0.58, 0.16, 0.58)); // blocky capital
return min(shaft, cap);
}
fn mod_cath_grove(p: vec3f) -> f32 {
let act = clamp(uni(45) * 2.0, 0.0, 1.0); // only past the corridor door
if (act < 0.01) { return 1e5; }
let pz = uni(3); // player world z
var d = 1e5;
let k0 = i32(floor((CATH_GROVE_Z0 - p.z) / 4.0)); // row at the march point
for (var dk = -1; dk <= 1; dk = dk + 1) {
let k = k0 + dk;
if (k < 0 || k > 8) { continue; }
let zr = CATH_GROVE_Z0 - f32(k) * 4.0;
let g = clamp((CATH_GROVE_R - abs(zr - pz)) / CATH_GROVE_FADE, 0.0, 1.0) * act;
if (g < 0.02) { continue; }
let H = CATH_GROVE_H * g;
if (k % 2 == 0) {
d = min(d, mod_cath_pier(p, -4.0, zr, H));
d = min(d, mod_cath_pier(p, 0.0, zr, H));
d = min(d, mod_cath_pier(p, 4.0, zr, H));
} else {
d = min(d, mod_cath_pier(p, -2.0, zr, H));
d = min(d, mod_cath_pier(p, 2.0, zr, H));
}
}
return d;
}
// ── COMPOSE: mod_cath = min() of the two mirrored arcades + the far gable + the
// weave-through forest ──
// world→avenue-local is a distance-preserving isometry (x-reflection + z-flip +
// translate) so each piece's SDF distance & AABB early-out stay TRUE in world space.
fn mod_cath(p: vec3f) -> f32 {
let zl = -56.0 - p.z; // avenue-local z: 0 (near) .. 40.0 (far)
let dArcR = mod_cath_arc(vec3f( p.x, p.y, zl)); // right colonnade (world x = +6.0)
let dArcL = mod_cath_arc(vec3f(-p.x, p.y, zl)); // left colonnade (world x = -6.0)
let dGab = mod_cath_gab(vec3f( p.x, p.y, zl)); // gable facade across the far end
let dGrove = mod_cath_grove(p); // staggered pier forest (world space)
return min(min(min(dArcR, dArcL), dGab), dGrove);
}
// ── THE GOO (Galen: "it lives as a layer of goo on the column and it jumps
// between them"). A quivering black-red parasite SHEATH that POSSESSES one of
// the grove piers (uni 64/65 snap to the pier grid), thorned all over, with a
// BIG EYE that tracks the player (uni(114) = live bearing). uni(112) rise ·
// uni(113) gather · uni(115) eye-hurt. It wraps the SAME rising pier the
// grove grows there (same fade law), so it always fits its host.
fn mod_cath_traitor(p: vec3f) -> f32 {
let trA = clamp(uni(112), 0.0, 1.0);
if (trA < 0.02) { return 1e5; }
let cx = uni(110); let cz = uni(111);
// bound: no pivot anymore — the goo never reaches past ~2.8 of its anchor
let bb = length(vec2f(p.x - cx, p.z - cz));
if (bb > 3.4) { return bb - 2.8; }
let pz = uni(3);
let g = clamp((CATH_GROVE_R - abs(cz - pz)) / CATH_GROVE_FADE, 0.0, 1.0) * clamp(uni(45) * 2.0, 0.0, 1.0);
let H = min(max(CATH_GROVE_H * g, 2.8), 5.5) * trA;
var q = vec3f(p.x - cx, p.y, p.z - cz);
let t9 = uni(0);
// AIRBORNE / SLUG (rise < 0.35): the THROWN MASS — a blob at height uni(120),
// elongated along its flight dir (uni(114)) by uni(116). This is the WHAM: the
// goo leaves the pier entirely and flies at the prey.
if (trA < 0.35) {
let bh = max(uni(120), 0.0);
let f2w = vec2f(sin(uni(114)), cos(uni(114)));
let fwd = q.x * f2w.x + q.z * f2w.y;
let sid = q.x * f2w.y - q.z * f2w.x;
let el = clamp(uni(116), 1.0, 2.2);
let swob = sin(fwd * 3.0 + t9 * 14.0) * 0.06 + sin(q.y * 4.0 - t9 * 11.0) * 0.05;
let bl = vec3f(fwd / el, (q.y - 0.30 - bh) * (2.4 - trA * 2.0 - min(bh, 1.0) * 0.6), sid);
return (length(bl) - (0.72 + trA * 0.5 + swob)) * 0.72;
}
// SHEATH ON THE PIER — with GATHER (uni(113) 0..1): before the wham the mass
// SQUEEZES UP its column — thin below, a huge quivering bulge at the crown.
// (It cannot lean back: the pier is stone. The goo is what moves.)
let gth = clamp(uni(113), 0.0, 1.0);
let yn = clamp(q.y / max(H, 0.5), 0.0, 1.0);
let ang = atan2(q.z, q.x);
let wob = (sin(q.y * 3.5 - t9 * 9.0) * 0.06 + sin(ang * 3.0 + t9 * 12.0 + q.y * 1.2) * 0.03) * (1.0 + gth * 2.2);
var rad = 0.55 + 0.06 * cos(yn * 3.14159) + wob;
rad = rad * (1.0 - gth * 0.5 * (1.0 - yn)) + gth * 0.42 * smoothstep(0.45, 1.0, yn);
var dd = max(length(vec2f(q.x, q.z)) - rad, abs(q.y - H * 0.5) - H * 0.5);
// THORNS — hashed spikes pushing out through the goo
let sg = floor(ang / 0.7854);
let yr = floor(q.y / 0.9);
let h1 = fract(sin(sg * 12.99 + yr * 78.23) * 43758.5453);
let ra = (sg + 0.5) * 0.7854 + (h1 - 0.5) * 0.5;
let rdir = vec2f(cos(ra), sin(ra));
let sp = vec3f(q.x - rdir.x * rad, q.y - (yr + 0.5) * 0.9 - (h1 - 0.5) * 0.35, q.z - rdir.y * rad);
let along = sp.x * rdir.x + sp.z * rdir.y;
let perp = length(vec3f(sp.x - rdir.x * along, sp.y, sp.z - rdir.y * along));
let spike = max(perp - max(0.15 - along * 0.26, 0.0), -along);
dd = min(dd, max(spike, along - 0.58));
return dd * 0.72;
}
fn mod_cath_traitor_eye(p: vec3f) -> f32 {
let trA = clamp(uni(112), 0.0, 1.0);
if (trA < 0.02) { return 1e5; }
let gth = clamp(uni(113), 0.0, 1.0);
var ey = vec3f(0.0);
if (trA < 0.35) {
// in flight the eye leads the thrown mass
ey = vec3f(uni(110) + sin(uni(114)) * 0.5, 0.30 + max(uni(120), 0.0), uni(111) + cos(uni(114)) * 0.5);
} else {
// on the pier it rides UP with the gather (the bulge carries it to the crown)
let pz = uni(3);
let g = clamp((CATH_GROVE_R - abs(uni(111) - pz)) / CATH_GROVE_FADE, 0.0, 1.0) * clamp(uni(45) * 2.0, 0.0, 1.0);
let H = min(max(CATH_GROVE_H * g, 2.8), 5.5) * trA;
let eyy = mix(2.1 * trA, H * 0.85, gth);
let wf = 0.62 + gth * 0.3;
ey = vec3f(uni(110) + sin(uni(114)) * wf, eyy, uni(111) + cos(uni(114)) * wf);
}
return length(p - ey) - 0.44;
}
module · ix
// IX — NODE⋈NODE INTERACTION EFFECTS (engine-category seed, v1 · Galen's brief Aug 6 2026).
// Nodes declare FIELDS (tagged regions of space) + REACTIONS (whenTag → effect) in the
// hook-side registry (globalThis.__IX). The ix-engine node computes overlaps and
// publishes ACTIVE OVERLAP RECORDS to its owned uniform lane u150-175. This module
// renders declared geometry CLIPPED to the overlap — a true intersection mask
// (SDF max), not a light falloff. ZERO world coordinates live in this file:
// every number arrives through the records.
// Record layout (2 slots): u150 = active count. slot s base b = 151 + s*12:
// b+0 regionId · b+1 coverType (1=sphere) · b+2..4 cover center · b+5 cover radius
// b+6..8 geo box center · b+9..11 geo box half-extents.
fn ix_box_d(p: vec3f, b: vec3f) -> f32 {
let q = abs(p) - b;
return length(max(q, vec3f(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
}
// distance to the COVER field's volume (sphere v1) — positive outside
fn ix_cover_d(b: i32, p: vec3f) -> f32 {
let c = vec3f(uni(b + 2), uni(b + 3), uni(b + 4));
return length(p - c) - uni(b + 5);
}
// the revealed room built from its DECLARED bounds: hollow shell + contents derived
// procedurally from center/half — no authored coordinates.
fn ix_room_geo(b: i32, p: vec3f) -> vec2f {
let c = vec3f(uni(b + 6), uni(b + 7), uni(b + 8));
let h = vec3f(uni(b + 9), uni(b + 10), uni(b + 11));
let q = p - c;
// region 2 — THE LURKER'S CYST: an organic dome grown from the avenue floor,
// egg-mounds inside glowing ember-red. Everything derived from declared c/h.
if (uni(b) > 1.5) {
// WALK-IN CYST (v2): tall dome, floor-open, ENTRANCE MOUTH toward +x (the avenue
// side), and a SPIRAL STAIR helixing up the core — the way UP into the dimension.
let rad = max(h.x, h.z);
let dome = abs(length(q * vec3f(1.0, 1.15, 1.0)) - rad) - 0.16;
var d2 = max(dome, -(q.y + h.y * 0.55));
let mq = q - vec3f(rad * 0.55, -h.y * 0.25, 0.0);
let mouth = length(vec2f(length(mq.yz * vec2f(0.8, 1.0)), max(0.0, -mq.x))) - rad * 0.46;
d2 = max(d2, -mouth);
// CROWN HOLE + STAIR COLUMN — the shaft pierces the dome; the way UP is visible
let crown = length(q.xz) - rad * 0.52;
d2 = max(d2, -max(crown, -(q.y - h.y * 0.25)));
var m2 = 16.0;
// BIGGER INSIDE (Galen): interior contents live in EXPANDED space — an exact
// SDF space-scale (eval at q*0.55, divide by 0.55) makes the shaft, spiral and
// eggs ~1.8x grander than the shell could hold. The unreal does not obey volume.
let qs = q * 0.55;
let shaft = max(max(length(qs.xz) - 0.26, -(qs.y + h.y)), qs.y - (h.y + 3.6)) / 0.55;
if (shaft < d2) { d2 = shaft; m2 = 16.0; }
// PERF (Galen: den lag) — the climbable spiral lives in the DEN INTERIOR you
// portal into; here the cyst only needs a HELICAL RIDGE hint on the shaft, not
// 14 box evals per pixel. One analytic groove instead of the loop.
let hh = atan2(qs.z, qs.x) + qs.y * 2.4;
let ridge = (abs(fract(hh / 6.28318) - 0.5) * 0.42 + abs(length(qs.xz) - rad * 0.30) - 0.10) / 0.55;
if (ridge < d2 && qs.y > -h.y + 0.2) { d2 = ridge; m2 = 9.0; }
let egg = (length(vec3f(abs(qs.x) - rad * 0.34, qs.y + h.y * 0.62, qs.z + rad * 0.32)) - rad * 0.2) / 0.55;
if (egg < d2) { d2 = egg; m2 = 9.0; }
return vec2f(d2, m2);
}
// shell walls ~0.16 thick, open top (inner carve extends up through the roof)
let outer = ix_box_d(q, h);
let inner = ix_box_d(q - vec3f(0.0, 0.3, 0.0), vec3f(h.x - 0.16, h.y, h.z - 0.16));
var d = max(outer, -inner);
var mat = 16.0; // black chitin, crawling red pulse
// altar slab at the heart
let alt = ix_box_d(q - vec3f(0.0, -h.y + 0.42, 0.0), vec3f(0.55, 0.42, 0.55));
if (alt < d) { d = alt; mat = 9.0; } // dying ember veins
// two watcher obelisks flanking the altar
let ob = ix_box_d(vec3f(abs(q.x) - h.x * 0.55, q.y - (-h.y + 0.95), q.z),
vec3f(0.16, 0.95, 0.16));
if (ob < d) { d = ob; mat = 16.0; }
return vec2f(d, mat);
}
// region 3 — THE CRYPT v3 (Galen: SIDE ROOM after a hallway — nothing on the nave
// floor). An EAST-WEST wing off the nave's west wall: hallway (east third, toward
// the nave) → chamber (west two-thirds) with the sarcophagus at its heart. All
// parts derive from the declared c/h; every SOLID stays inside the declared box so
// the broad-phase bound in mod_ix_geo stays valid (air carves may exceed it).
// Collision = the vf-frame walkable pocket gated on __VF_CRYPT0 (the house pattern),
// NOT per-node push-out.
fn ix_crypt_geo(b: i32, p: vec3f) -> vec2f {
let c = vec3f(uni(b + 6), uni(b + 7), uni(b + 8));
let h = vec3f(uni(b + 9), uni(b + 10), uni(b + 11));
let q = p - c;
// CHAMBER — west two-thirds: hollow stone box
let cc = q - vec3f(-h.x / 3.0, 0.0, 0.0);
let chh = vec3f(2.0 * h.x / 3.0, h.y, 0.96 * h.z);
var d = max(ix_box_d(cc, chh), -ix_box_d(cc, vec3f(chh.x - 0.2, chh.y - 0.05, chh.z - 0.2)));
var mat = 11.0; // dark cathedral stone
// HALLWAY — east third, toward the nave: tube, OPEN at the nave (+x) end
let hc = q - vec3f(2.0 * h.x / 3.0, -0.1 * h.y, 0.0);
let hhh = vec3f(h.x / 3.0, 0.8 * h.y, 0.9);
let hall = max(ix_box_d(hc, hhh), -ix_box_d(hc - vec3f(0.5, 0.0, 0.0), vec3f(hhh.x + 0.5, hhh.y - 0.05, hhh.z - 0.18)));
if (hall < d) { d = hall; mat = 11.0; }
// JUNCTION DOORWAY: carve the chamber's east face open into the hallway
d = max(d, -ix_box_d(q - vec3f(h.x / 3.0, -0.2 * h.y, 0.0), vec3f(0.55, 0.6 * h.y, 0.75)));
// SARCOPHAGUS at the chamber heart
let sc = cc - vec3f(0.0, -h.y + 0.5, 0.0);
// the box LOWERS as it opens (u177): full 0.5 tall closed → a 0.12 lip open, so
// there is no vertical wall to climb — you step over a curb, not a fence.
let lo0 = clamp(uni(177), 0.0, 1.0);
let boxHY = 0.5 - lo0 * 0.40;
let sarc = ix_box_d(sc - vec3f(0.0, (boxHY - 0.5), 0.0), vec3f(0.72, boxHY, 1.3));
if (sarc < d) { d = sarc; mat = 10.0; }
// THE LID (Galen): u177 slides it open; the open box is a hollow VOID MOUTH
// (weave floor, mat 18) — the dimension you can JUMP INTO.
let lidOpen = lo0;
if (lidOpen > 0.4) {
// the VOID WELL — a bottomless black shaft down the middle you fall through
d = max(d, -ix_box_d(sc - vec3f(0.0, -0.9, 0.0), vec3f(0.55, 1.6, 1.05) * min(lidOpen * 1.8, 1.0)));
}
let lid = ix_box_d(sc - vec3f(-lidOpen * 1.45, 0.5 + lidOpen * 0.12, 0.0), vec3f(0.6, 0.06, 1.15));
if (lid < d) { d = lid; mat = 9.0; } // ember lid-seam glow
return vec2f(d, mat);
}
// DOOR CARVE for w3_map (coordinate-free): while a region-3 record is active, an
// air box at the wing's +x face punches the nave west wall so the hallway opens.
// No record → 1e5 → the wall is whole. rooms.wgsl applies `d = max(d, -mod_ix_door(p))`.
fn mod_ix_door(p: vec3f) -> f32 {
let n = i32(uni(150));
if (n < 1) { return 1e5; }
var carve = 1e5;
for (var s = 0; s < 2; s++) {
if (s >= n) { break; }
let b = 151 + s * 12;
if (uni(b) < 2.5) { continue; } // crypt (region 3) doors only
let c = vec3f(uni(b + 6), uni(b + 7), uni(b + 8));
let h = vec3f(uni(b + 9), uni(b + 10), uni(b + 11));
carve = min(carve, ix_box_d(p - vec3f(c.x + h.x, c.y - 0.1 * h.y, c.z), vec3f(1.3, 0.75 * h.y, 0.85)));
}
return carve;
}
// EXPORT to w3_map: nearest declared geometry, CLIPPED to its overlap (SDF max —
// the surface exists exactly where the cover field intersects the declared bounds).
fn mod_ix_geo(p: vec3f) -> vec2f {
let n = i32(uni(150));
if (n < 1) { return vec2f(1e5, 0.0); }
var best = vec2f(1e5, 0.0);
for (var s = 0; s < 2; s++) {
if (s >= n) { break; }
let b = 151 + s * 12;
// broad-phase: bound reject (valid lower bound of any clipped surface inside)
let c = vec3f(uni(b + 6), uni(b + 7), uni(b + 8));
let h = vec3f(uni(b + 9), uni(b + 10), uni(b + 11));
let bb = ix_box_d(p - c, h);
if (bb > 2.5) {
if (bb < best.x) { best = vec2f(bb, 0.0); }
continue;
}
var g: vec2f;
if (uni(b) > 2.5) { g = ix_crypt_geo(b, p); } // region 3 = crypt (room+hallway)
else { g = ix_room_geo(b, p); } // region 1 = box room, 2 = cyst
let dc = max(g.x, ix_cover_d(b, p));
if (dc < best.x) { best = vec2f(dc, g.y); }
}
return best;
}
module · maze
// GENERATED by swarm/generate-lair-maze.mjs (seed 1337) — DO NOT hand-edit; regenerate.
// THE LAIR MAZE — a GROTESQUE labyrinth in the risen-nave avenue, rendered only when
// vf_warp>1.5 (the lair). Twisted bulbous bone-columns at grid vertices + fleshy
// displaced wall panels on standing edges. Walls RISE with uni(48) (grow in front of
// you as you enter). mod_maze(p) returns signed distance; its base hull (plain box+
// post, before displacement) is mirrored by movement.mjs mazeBlocked — displacement
// stays within the body radius so collision never disagrees with where you can walk.
const MZ_NX: i32 = 4; const MZ_NZ: i32 = 14;
const MZ_C: f32 = 2.675; const MZ_X0: f32 = -5.35; const MZ_Z0: f32 = -56;
const MZ_WH: f32 = 0.17; const MZ_PR: f32 = 0.36; const MZ_TOP: f32 = 2.8; // LOWERED (Galen Jul 31: lower the labyrinth walls)
const MZ_EXITX: f32 = 4.012; // exit doorway centre x (far gable end)
const MZ_V = array<u32, 3>(3623993343u, 4278307250u, 63u); // vertical walls vIdx=i*NZ+j
const MZ_H = array<u32, 2>(1861831249u, 39926629u); // horizontal walls hIdx=i*(NZ+1)+j
fn mz_vOn(i: i32, j: i32) -> bool { if (i < 0 || i > MZ_NX || j < 0 || j >= MZ_NZ) { return true; } let n = i * MZ_NZ + j; return ((MZ_V[n / 32] >> u32(n % 32)) & 1u) == 1u; }
fn mz_hOn(i: i32, j: i32) -> bool { if (i < 0 || i >= MZ_NX || j < 0 || j > MZ_NZ) { return true; } let n = i * (MZ_NZ + 1) + j; return ((MZ_H[n / 32] >> u32(n % 32)) & 1u) == 1u; }
// ── cheap self-contained noise (nothing before this module provides one) ──
fn mz_h3(p: vec3f) -> f32 { return fract(sin(dot(p, vec3f(12.9898, 78.233, 37.719))) * 43758.5453); }
fn mz_vn(p: vec3f) -> f32 {
let i = floor(p); let f = fract(p); let u = f * f * (3.0 - 2.0 * f);
let a = mix(mz_h3(i + vec3f(0.,0.,0.)), mz_h3(i + vec3f(1.,0.,0.)), u.x);
let b = mix(mz_h3(i + vec3f(0.,1.,0.)), mz_h3(i + vec3f(1.,1.,0.)), u.x);
let c = mix(mz_h3(i + vec3f(0.,0.,1.)), mz_h3(i + vec3f(1.,0.,1.)), u.x);
let d = mix(mz_h3(i + vec3f(0.,1.,1.)), mz_h3(i + vec3f(1.,1.,1.)), u.x);
return mix(mix(a, b, u.y), mix(c, d, u.y), u.z);
}
// per-cell rise: driven by the PLAYER'S PROXIMITY to this row (uni(3)=player z),
// NOT a global uni(48) penetration clock — so the labyrinth builds itself right
// around you in the dark, close and sudden, and sinks back as you move on. The
// reveal radius is deliberately TIGHT so walls slam up just ahead, never far down
// the avenue. Collision (movement.mjs mazeBlocked) is the always-solid footprint;
// MAZE_REVEAL_R comfortably exceeds the reach at which the player can touch a wall
// (current cell + neighbours, ≲4u), so solid and visible never disagree where it
// matters. NB: swarm/generate-lair-maze.mjs still emits the old uni(48) clock —
// re-apply this proximity model there if the maze is regenerated.
const MAZE_REVEAL_R: f32 = 7.0; // world-u: growth rises within this of the player
const MAZE_REVEAL_FADE: f32 = 4.0; // fully risen this much closer
// proximity rise from a wall/post's OWN world-z. CONTINUOUS in z, so a wall shared
// by two cells gets ONE height from both sides — the old per-cell height made the
// same seam-wall two different heights and the disagreeing top-slice read as a
// SEE-THROUGH RECTANGLE. Evaluating by the element's own z closes that.
fn mz_growZ(zc: f32) -> f32 {
return 1.0; // STATIC (Galen Jul 31): the walls stand full-height, they no longer rise as you approach
// was: clamp((MAZE_REVEAL_R - abs(zc - uni(3))) / MAZE_REVEAL_FADE, 0.0, 1.0);
}
fn mz_grow(j: i32) -> f32 { return mz_growZ(MZ_Z0 - (f32(j) + 0.5) * MZ_C); }
// GROTESQUE BONE-COLUMN at (cx,cz): twists with height, bulges in ragged nodes, noise-pocked.
fn mz_post(p: vec3f, cx: f32, cz: f32, H: f32, seed: f32) -> f32 {
var q = vec3f(p.x - cx, p.y, p.z - cz);
let tw = q.y * (0.35 + seed * 0.5); // sinewy twist
let ca = cos(tw); let sa = sin(tw);
let rx = q.x * ca - q.z * sa; let rz = q.x * sa + q.z * ca;
let node = 0.5 + 0.5 * sin(q.y * 2.4 + seed * 6.28); // vertebra bulges up the shaft
let bulge = MZ_PR * (0.62 + 0.42 * node + 0.18 * seed);
let r = length(vec2f(rx, rz)) - bulge; // PERF: dropped per-step value-noise (mz_vn) from the SDF — twist+vertebra keep the grotesque shape
let dy = abs(q.y - H * 0.5) - H * 0.5;
return max(r, dy);
}
// FLESHY WALL PANEL: box hull minus rib ridges + noise bumps (all within body radius).
fn mz_wall(p: vec3f, along_z: bool, wpos: f32, cen: f32, H: f32, seed: f32) -> f32 {
var dperp: f32; var dpar: f32;
if (along_z) { dperp = abs(p.x - wpos) - MZ_WH; dpar = abs(p.z - cen) - MZ_C * 0.5; }
else { dperp = abs(p.z - wpos) - MZ_WH; dpar = abs(p.x - cen) - MZ_C * 0.5; }
var d = length(max(vec2f(dperp, dpar), vec2f(0.0))) + min(max(dperp, dpar), 0.0);
let along = select(p.x, p.z, along_z);
let ribs = 0.055 * sin(p.y * 3.6 + seed * 3.0) * (0.5 + 0.5 * sin(along * 2.1)); // horizontal sinew ribs
d = d - ribs - 0.02; // PERF: dropped per-step value-noise (mz_vn); ribs keep the sinew relief // grotesque relief, stays < body R
let dy = p.y - H;
return max(d, dy);
}
// the maze SDF: grown grotesque walls + posts of the point's cell + an exit doorway arch.
fn mod_maze(p: vec3f) -> f32 {
let fi = (p.x - MZ_X0) / MZ_C; let fj = (MZ_Z0 - p.z) / MZ_C;
let i = clamp(i32(floor(fi)), 0, MZ_NX - 1); let j = clamp(i32(floor(fj)), 0, MZ_NZ - 1);
var d = 1e5;
let zc = MZ_Z0 - (f32(j) + 0.5) * MZ_C; let xc = MZ_X0 + (f32(i) + 0.5) * MZ_C;
// Each wall/post takes its height from ITS OWN world-z (continuous across the seam),
// gated individually — so a wall shared by two cells is ONE height from both sides.
// (The old single per-cell Hc made a seam-wall two heights → see-through rectangle.)
let gV = mz_growZ(zc); // vertical runs — cell-centre z (shared with the i-neighbour)
if (gV > 0.02) {
let Hv = MZ_TOP * gV;
if (mz_vOn(i, j)) { d = min(d, mz_wall(p, true, MZ_X0 + f32(i) * MZ_C, zc, Hv, mz_h3(vec3f(f32(i), f32(j), 1.0)))); }
if (mz_vOn(i + 1, j)) { d = min(d, mz_wall(p, true, MZ_X0 + f32(i + 1) * MZ_C, zc, Hv, mz_h3(vec3f(f32(i + 1), f32(j), 2.0)))); }
}
let zH0 = MZ_Z0 - f32(j) * MZ_C; let gH0 = mz_growZ(zH0); // each horizontal run at its OWN z
let zH1 = MZ_Z0 - f32(j + 1) * MZ_C; let gH1 = mz_growZ(zH1);
if (gH0 > 0.02 && mz_hOn(i, j)) { d = min(d, mz_wall(p, false, zH0, xc, MZ_TOP * gH0, mz_h3(vec3f(f32(i), f32(j), 3.0)))); }
if (gH1 > 0.02 && mz_hOn(i, j + 1)) { d = min(d, mz_wall(p, false, zH1, xc, MZ_TOP * gH1, mz_h3(vec3f(f32(i), f32(j + 1), 4.0)))); }
for (var ci = 0; ci <= 1; ci = ci + 1) { for (var cj = 0; cj <= 1; cj = cj + 1) {
let px = MZ_X0 + f32(i + ci) * MZ_C; let pz = MZ_Z0 - f32(j + cj) * MZ_C;
let gP = mz_growZ(pz);
if (gP > 0.02) { d = min(d, mz_post(p, px, pz, MZ_TOP * gP * 1.08, mz_h3(vec3f(px, pz, 7.0)))); }
}}
return d;
}
// EXIT DOORWAY — a lit pointed-arch portal at the far gable end (z≈gable), centred on
// the exit lane. Returns the arch-FRAME distance; mat handled by the caller (emissive).
fn mod_maze_door(p: vec3f) -> f32 {
let zc = MZ_Z0 - f32(MZ_NZ) * MZ_C - 0.4; // just past the last cell row
let lx = abs(p.x - MZ_EXITX);
// pointed-arch opening: two leaning planes meeting at an apex over a 1.5-wide gap
let open = max(lx - 1.5 + max(p.y - 2.2, 0.0) * 1.1, -(p.y - 0.05));
let slab = max(abs(p.z - zc) - 0.35, max(lx - 2.6, p.y - 4.2));
return max(slab, -open);
}
module · world3
// WORLD3 — the cafe's shared 3D raymarching kit (canonical copy).
// Everything ONE DAY, TIDEGLASS, and MARIONETTES 3D each hand-rolled, extracted
// once: camera, SDF primitives, domain ops, marcher, normals, soft shadows,
// AO, and a standard light rig. Ship it in your scene as a module:
//
// { "type": "define_module", "name": "world3", "wgsl": <this file> }
//
// THE ONE CONTRACT — your scene defines the world map in its OWN module:
//
// fn w3_map(p: vec3f) -> vec2f // returns (signed distance, material id)
//
// WGSL resolves module-scope functions in any order, so the kit's marchers can
// call w3_map before your module defines it. A scene that ships world3 without
// defining w3_map will not compile — the contract is load-bearing.
//
// CAMERA CONVENTION (whiteboard rows 60–62, so hooks can drive the eye):
// uni4(60) = ro.xyz, fov · uni4(61) = target.xyz, roll(unused)
// A hook that writes these makes any world3 scene orbit/walk for free.
//
// Typical visual:
// let ro = uni4(60).xyz; let fov = max(uni4(60).w, 0.6);
// let rd = mod_w3_ray(uv, ro, uni4(61).xyz, fov);
// let hit = mod_w3_march(ro, rd, 0.1, 60.0, 96);
// if (hit.x > 0.0) {
// let pos = ro + rd * hit.x;
// let n = mod_w3_nrm(pos, 0.02);
// let sh = mod_w3_shadow(pos + n * 0.05, sunDir, 30.0, 8.0);
// let ao = mod_w3_ao(pos, n);
// col = mod_w3_light(albedoFor(i32(hit.y)), n, rd, sunDir, sunCol, skyCol, sh, ao);
// }
// Output linear HDR — the engine's ACES + bloom do the grading.
// ── camera ─────────────────────────────────────────────────────────────────
// Ray through a screen point for a look-at camera. uv is the visual's -1..1
// (y down — the flip is handled here, pass it raw).
fn mod_w3_ray(uv: vec2f, ro: vec3f, ta: vec3f, fov: f32) -> vec3f {
let fw = normalize(ta - ro);
let rt = normalize(cross(fw, vec3f(0.0, 1.0, 0.0)));
let up = cross(rt, fw);
return normalize(uv.x * rt * fov - uv.y * up * fov + fw);
}
// ── SDF primitives (3D) ────────────────────────────────────────────────────
fn mod_w3_sphere(p: vec3f, r: f32) -> f32 { return length(p) - r; }
fn mod_w3_box(p: vec3f, b: vec3f) -> f32 {
let d = abs(p) - b;
return length(max(d, vec3f(0.0))) + min(max(d.x, max(d.y, d.z)), 0.0);
}
fn mod_w3_rbox(p: vec3f, b: vec3f, r: f32) -> f32 {
return mod_w3_box(p, b - vec3f(r)) - r;
}
fn mod_w3_capsule(p: vec3f, a: vec3f, b: vec3f, r: f32) -> f32 {
let pa = p - a;
let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
// vertical capped cylinder: half-height h, radius r
fn mod_w3_cyl(p: vec3f, h: f32, r: f32) -> f32 {
let d = abs(vec2f(length(p.xz), p.y)) - vec2f(r, h);
return min(max(d.x, d.y), 0.0) + length(max(d, vec2f(0.0)));
}
// tapered vertical cylinder: radius r0 at -h → r1 at +h (towers, spires)
fn mod_w3_cone(p: vec3f, h: f32, r0: f32, r1: f32) -> f32 {
let t = clamp((p.y + h) / (2.0 * h), 0.0, 1.0);
let d = abs(vec2f(length(p.xz), p.y)) - vec2f(mix(r0, r1, t), h);
return min(max(d.x, d.y), 0.0) + length(max(d, vec2f(0.0)));
}
fn mod_w3_torus(p: vec3f, R: f32, r: f32) -> f32 {
return length(vec2f(length(p.xz) - R, p.y)) - r;
}
fn mod_w3_octa(p: vec3f, s: f32) -> f32 {
let q = abs(p);
return (q.x + q.y + q.z - s) * 0.57735027;
}
fn mod_w3_plane(p: vec3f, n: vec3f, d: f32) -> f32 { return dot(p, n) + d; }
// ── skeleton assembly (nodes + struts + tissue) ────────────────────────────
// Build forms as a GRAPH: struts between shared node points, joined with
// opSmoothUnion (the "linkage tissue"). Gaps become impossible by construction
// — parts share endpoints instead of hoping coordinates line up. Use tissue k
// at structural joints (0.2–0.5) and hard min for edges that must stay crisp.
// exact closed-form distance to a quadratic bezier strut (S start, Ctl control,
// E end, radius r) — the connective arc: vaults, arches, branches, cables
fn mod_w3_bezStrut(pos: vec3f, S: vec3f, Ctl: vec3f, E: vec3f, r: f32) -> f32 {
let a = Ctl - S;
let b = S - 2.0 * Ctl + E;
let c = a * 2.0;
let d = S - pos;
let bb = dot(b, b);
if (bb < 1e-9) {
let pa = pos - S;
let ba = E - S;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
let kk = 1.0 / bb;
let kx = kk * dot(a, b);
let ky = kk * (2.0 * dot(a, a) + dot(d, b)) / 3.0;
let kz = kk * dot(d, a);
let pp = ky - kx * kx;
let qq = kx * (2.0 * kx * kx - 3.0 * ky) + kz;
let h = qq * qq + 4.0 * pp * pp * pp;
var res: f32;
if (h >= 0.0) {
let hs = sqrt(h);
let x1 = (hs - qq) / 2.0;
let x2 = (-hs - qq) / 2.0;
let t = clamp(sign(x1) * pow(abs(x1), 0.3333333) + sign(x2) * pow(abs(x2), 0.3333333) - kx, 0.0, 1.0);
let g = d + (c + b * t) * t;
res = dot(g, g);
} else {
let z = sqrt(-pp);
let v = acos(qq / (pp * z * 2.0)) / 3.0;
let m = cos(v);
let n = sin(v) * 1.7320508;
let t1 = clamp((m + m) * z - kx, 0.0, 1.0);
let t2 = clamp((-n - m) * z - kx, 0.0, 1.0);
let g1 = d + (c + b * t1) * t1;
let g2 = d + (c + b * t2) * t2;
res = min(dot(g1, g1), dot(g2, g2));
}
return sqrt(res) - r;
}
// tapered strut between nodes (columns, spires, limbs): radius r1 at a → r2 at b
fn mod_w3_taperStrut(p: vec3f, a: vec3f, b: vec3f, r1: f32, r2: f32) -> f32 {
let pa = p - a;
let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - mix(r1, r2, h);
}
// round (Roman) arch opening — subtract from a wall. Origin at base center:
// straight sides height h, half-width w (semicircle top radius w), half-depth d in z
fn mod_w3_arch(p: vec3f, w: f32, h: f32, d: f32) -> f32 {
let dxy = abs(p.xy - vec2f(0.0, h * 0.5)) - vec2f(w, h * 0.5);
let rect2 = length(max(dxy, vec2f(0.0))) + min(max(dxy.x, dxy.y), 0.0);
let top2 = length(p.xy - vec2f(0.0, h)) - w;
let d2 = min(rect2, top2);
let wz = vec2f(d2, abs(p.z) - d);
return min(max(wz.x, wz.y), 0.0) + length(max(wz, vec2f(0.0)));
}
// Gothic lancet (ogival/pointed) arch profile: straight sides |x|<=w up to h,
// two-arc point peaking ph above h. 2D — extrude with mod_w3_lancet.
fn mod_w3_lancet2(q: vec2f, w: f32, h: f32, ph: f32) -> f32 {
let R = (w * w + ph * ph) / (2.0 * w);
let c = R - w;
let dxy = abs(q - vec2f(0.0, h * 0.5)) - vec2f(w, h * 0.5);
let rect = length(max(dxy, vec2f(0.0))) + min(max(dxy.x, dxy.y), 0.0);
let arcs = max(length(q - vec2f(-c, h)) - R, length(q - vec2f(c, h)) - R);
let top = max(arcs, h - q.y);
return min(rect, top);
}
// Gothic lancet arch opening in 3D — subtract from a wall. Base center origin,
// half-width w, straight height h, point rises ph above h, half-depth d in z
fn mod_w3_lancet(p: vec3f, w: f32, h: f32, ph: f32, d: f32) -> f32 {
let d2 = mod_w3_lancet2(p.xy, w, h, ph);
let wz = vec2f(d2, abs(p.z) - d);
return min(max(wz.x, wz.y), 0.0) + length(max(wz, vec2f(0.0)));
}
// ── domain ops ─────────────────────────────────────────────────────────────
fn mod_w3_rotX(p: vec3f, a: f32) -> vec3f {
let c = cos(a); let s = sin(a);
return vec3f(p.x, c * p.y - s * p.z, s * p.y + c * p.z);
}
fn mod_w3_rotY(p: vec3f, a: f32) -> vec3f {
let c = cos(a); let s = sin(a);
return vec3f(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
}
fn mod_w3_rotZ(p: vec3f, a: f32) -> vec3f {
let c = cos(a); let s = sin(a);
return vec3f(c * p.x - s * p.y, s * p.x + c * p.y, p.z);
}
// infinite repetition on chosen axes (c = cell size per axis; 0 = no repeat)
fn mod_w3_repeat(p: vec3f, c: vec3f) -> vec3f {
var q = p;
if (c.x > 0.0) { q.x = (fract(p.x / c.x + 0.5) - 0.5) * c.x; }
if (c.y > 0.0) { q.y = (fract(p.y / c.y + 0.5) - 0.5) * c.y; }
if (c.z > 0.0) { q.z = (fract(p.z / c.z + 0.5) - 0.5) * c.z; }
return q;
}
// polar repetition around Y: n copies; returns p in the first wedge
fn mod_w3_polar(p: vec3f, n: f32) -> vec3f {
let ang = 6.2831853 / n;
let a = atan2(p.z, p.x);
let r = length(p.xz);
let a2 = (fract(a / ang + 0.5) - 0.5) * ang;
return vec3f(cos(a2) * r, p.y, sin(a2) * r);
}
// ── the marcher family (all call YOUR w3_map) ──────────────────────────────
// sphere-trace: returns (t, material) on hit, (-1, -1) on miss
fn mod_w3_march(ro: vec3f, rd: vec3f, tmin: f32, tmax: f32, steps: i32) -> vec2f {
// Enhanced sphere tracing (Keinert et al. 2014). Over-relax the step (1.4x) so
// open space — the long sightlines in the grown cathedral / risen nave — is
// crossed in fewer iterations. The sphere-overlap test (radius+prevRadius <
// stepLen) detects an over-step and retreats, dropping omega to 0.9 — the
// field's proven-safe factor for the non-Lipschitz noise bone-columns — for
// the rest of that ray, so accuracy is never worse than the old constant-0.9
// march. Verified (Jul 28 2026, software-GPU A/B): cathedral + lair render
// PIXEL-IDENTICAL to the old march (0.00 lum/coverage/quadrant diff, zero
// tunneling on the noise columns) while the cathedral renders ~1.26x faster.
var omega = 1.4;
var t = tmin;
var prevRadius = 0.0;
var stepLen = 0.0;
for (var i = 0; i < 256; i++) {
if (i >= steps) { break; }
let dm = w3_map(ro + rd * t);
let radius = dm.x;
let sorFail = (omega > 1.0) && ((radius + prevRadius) < stepLen);
if (sorFail) {
stepLen = stepLen - omega * stepLen; // retreat 0.4x of the over-step
omega = 0.9; // conservative for the rest of this ray
} else {
if (radius < 0.001 * t + 0.003) { return vec2f(t, dm.y); }
stepLen = max(radius * omega, 0.004);
}
prevRadius = radius;
t = t + stepLen;
if (t > tmax) { break; }
}
return vec2f(-1.0, -1.0);
}
// tetrahedral 4-tap normal
fn mod_w3_nrm(p: vec3f, eps: f32) -> vec3f {
let k = vec2f(1.0, -1.0);
return normalize(
k.xyy * w3_map(p + k.xyy * eps).x +
k.yyx * w3_map(p + k.yyx * eps).x +
k.yxy * w3_map(p + k.yxy * eps).x +
k.xxx * w3_map(p + k.xxx * eps).x);
}
// soft shadow toward a light: k = penumbra hardness (8 soft … 32 crisp)
// MORPH PERF (Galen: "slow the morph so it doesn't lag"): shadows sample
// w3_map_core, NOT the morphing w3_map — during the reality tween every
// w3_map call marches TWO scenes, and shadows were paying that double cost
// on every penumbra tap. A melting scene's shadows tracking only the NEW
// reality is imperceptible; primary rays + normals keep the full blend.
fn mod_w3_shadow(p: vec3f, ld: vec3f, tmax: f32, k: f32) -> f32 {
var t = 0.03;
var sh = 1.0;
for (var i = 0; i < 16; i++) { // PERF: 24→16; rays that hit an occluder early-out anyway, so this only trims lit-area penumbra taps
let d = w3_map_core(p + ld * t).x;
sh = min(sh, k * d / t);
t = t + clamp(d, 0.02, 0.8);
if (sh < 0.02 || t > tmax) { break; }
}
return clamp(sh, 0.0, 1.0);
}
// 4-tap ambient occlusion along the normal (w3_map_core: same morph-perf rule as shadows)
fn mod_w3_ao(p: vec3f, n: vec3f) -> f32 {
var occ = 0.0;
var w = 1.0;
for (var i = 1; i <= 4; i++) {
let h = 0.04 * f32(i * i);
occ = occ + w * (h - w3_map_core(p + n * h).x);
w = w * 0.65;
}
return clamp(1.0 - 2.2 * occ, 0.0, 1.0);
}
// ── shading ────────────────────────────────────────────────────────────────
fn mod_w3_fresnel(n: vec3f, rd: vec3f, f0: f32) -> f32 {
return f0 + (1.0 - f0) * pow(1.0 - clamp(dot(n, -rd), 0.0, 1.0), 5.0);
}
// the standard rig: sun key + sky fill + bounce, shadow and AO applied where
// each belongs (shadow kills the key, AO dims the fill), plus a spec lobe
fn mod_w3_light(alb: vec3f, n: vec3f, rd: vec3f, sunDir: vec3f, sunCol: vec3f, skyCol: vec3f, sh: f32, ao: f32) -> vec3f {
let dif = clamp(dot(n, sunDir), 0.0, 1.0);
let sky = 0.5 + 0.5 * n.y;
let bou = clamp(-n.y, 0.0, 1.0) * 0.3;
var c = alb * (sunCol * dif * sh + skyCol * sky * ao + skyCol * bou * ao);
let hal = normalize(sunDir - rd);
let spe = pow(clamp(dot(n, hal), 0.0, 1.0), 32.0) * dif * sh;
c = c + sunCol * spe * 0.5 * mod_w3_fresnel(n, rd, 0.04);
return c;
}
// aerial perspective: fold a hit color into the sky with distance
fn mod_w3_fog(c: vec3f, skyC: vec3f, t: f32, density: f32) -> vec3f {
return mix(c, skyC, 1.0 - exp(-t * t * density));
}
// ── TRUNCATED DODECAHEDRON (dodeca-arena) — appended so rooms (loads after) can call it ──
// vf_dodeca — a TRUNCATED DODECAHEDRON chamber (12 decagon + 20 triangle faces).
// mod_dodeca(p, s) = SOLID SDF centered at origin (negative inside), s = inradius
// scale. Rooms carves the air pocket with max(d, -mod_dodeca(p-center, s)).
// Face normals are the icosahedron (decagon) + dodecahedron (triangle) vertex
// directions — generated + unit-tested by gen-dodeca.mjs (12+20 faces).
const DODEC_RDEC: f32 = 1.000;
const DODEC_RTRI: f32 = 1.120;
fn mod_octroom(p: vec3f, R: f32, H: f32) -> f32 {
let a = abs(p.x); let b = abs(p.z);
let oct = max(max(a, b), (a + b) * 0.70710678) - R; // regular octagon in xz
return max(oct, max(-p.y, p.y - H)); // negative inside: in the octagon AND y in [0,H]
}
fn mod_dodeca(p: vec3f, s: f32) -> f32 {
var d = -1e9;
d = max(d, dot(p, vec3f(0.000000, 0.525731, 0.850651)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.000000, 0.525731, -0.850651)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.000000, -0.525731, 0.850651)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.000000, -0.525731, -0.850651)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.525731, 0.850651, 0.000000)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.525731, -0.850651, 0.000000)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(-0.525731, 0.850651, 0.000000)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(-0.525731, -0.850651, 0.000000)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.850651, 0.000000, 0.525731)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.850651, 0.000000, -0.525731)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(-0.850651, 0.000000, 0.525731)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(-0.850651, 0.000000, -0.525731)) - s * DODEC_RDEC);
d = max(d, dot(p, vec3f(0.577350, 0.577350, 0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.577350, 0.577350, -0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.577350, -0.577350, 0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.577350, -0.577350, -0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.577350, 0.577350, 0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.577350, 0.577350, -0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.577350, -0.577350, 0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.577350, -0.577350, -0.577350)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.000000, 0.356822, 0.934172)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.000000, 0.356822, -0.934172)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.000000, -0.356822, 0.934172)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.000000, -0.356822, -0.934172)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.356822, 0.934172, 0.000000)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.356822, -0.934172, 0.000000)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.356822, 0.934172, 0.000000)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.356822, -0.934172, 0.000000)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.934172, 0.000000, 0.356822)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(0.934172, 0.000000, -0.356822)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.934172, 0.000000, 0.356822)) - s * DODEC_RTRI);
d = max(d, dot(p, vec3f(-0.934172, 0.000000, -0.356822)) - s * DODEC_RTRI);
return d;
}
module · anim3
// ANIM3 — articulated-chain animation kit (canonical copy).
// The layer between skel-lib (creature rigs) and world3 (raymarched space):
// two-bone IK, tapered limb SDFs, gait oscillators with planted feet, pose
// paths, and aim frames — everything needed to make a marched body MOVE well.
//
// { "type": "define_module", "name": "anim3", "wgsl": <this file> }
//
// Design: everything is STATELESS — pure functions of time and parameters, so
// they run per-pixel in a visual with no CPU sync. Drive the inputs (targets,
// phases, headings) from a step hook via the whiteboard or the population
// buffer; the shader poses the body.
//
// The crowd pattern: hook publishes gpuPopulation entries [x, y, heading, phase]
// → the visual loops pop(i), builds each body in its local frame with
// mod_a3_gait(phase …) driving the legs. 4095 walkers, one dispatch.
// ── two-bone IK ────────────────────────────────────────────────────────────
// Classic solver: a chain root→mid→tip with segment lengths l1, l2 reaching
// for `target`. Returns the MID joint (elbow/knee). `pole` bends the joint
// toward it (knee forward, elbow back) — give it a point, not a direction.
// Unreachable targets clamp to full extension; degenerate poles self-heal.
fn mod_a3_ik2(root: vec3f, tgt: vec3f, l1: f32, l2: f32, pole: vec3f) -> vec3f {
var to = tgt - root;
var d = length(to);
let maxR = l1 + l2 - 0.0001;
d = clamp(d, abs(l1 - l2) + 0.0001, maxR);
let dir = to / max(length(to), 0.0001);
// law of cosines: distance from root to the mid joint's projection
let a = (l1 * l1 - l2 * l2 + d * d) / (2.0 * d);
let h = sqrt(max(l1 * l1 - a * a, 0.0));
// bend plane: contains the chain axis, leans toward the pole
var side = pole - root - dir * dot(pole - root, dir);
let sl = length(side);
if (sl < 0.001) { side = vec3f(0.0, 0.0, 1.0) - dir * dir.z; } else { side = side / sl; }
return root + dir * a + normalize(side) * h;
}
// ── limb geometry ──────────────────────────────────────────────────────────
// tapered capsule: radius r0 at `a` → r1 at `b` (thigh→ankle, arm→wrist)
fn mod_a3_bone(p: vec3f, a: vec3f, b: vec3f, r0: f32, r1: f32) -> f32 {
let pa = p - a;
let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - mix(r0, r1, h);
}
// joint ball — drop one at every hinge to keep silhouettes smooth
fn mod_a3_joint(p: vec3f, at: vec3f, r: f32) -> f32 { return length(p - at) - r; }
// ── gait ───────────────────────────────────────────────────────────────────
// The planted-foot law (lifted from skel-lib's ladder): phase advances in
// STRIDE CYCLES (hook: ph += speed / strideLen * dt). Within one cycle a foot
// is planted for `duty` (moving backward at exactly body speed → zero world
// velocity) and swings forward for the rest, lifted on a sine arc.
// Returns (alongOffset, lift): foot position relative to its rest point,
// in units of strideLen.
fn mod_a3_gait(phase: f32, duty: f32) -> vec2f {
let c = fract(phase);
if (c < duty) {
// stance: linear back-travel, no lift — the foot OWNS the ground
let s = c / duty;
return vec2f(0.5 - s, 0.0);
}
// swing: forward and up
let s = (c - duty) / (1.0 - duty);
return vec2f(-0.5 + s, sin(s * 3.14159) * 1.0);
}
// eased oscillator for secondary motion (tails, breathing, sway):
// sin with adjustable sharpness — sharp > 1 dwells at the extremes
fn mod_a3_sway(t: f32, freq: f32, sharp: f32) -> f32 {
let s = sin(t * freq);
return sign(s) * pow(abs(s), 1.0 / max(sharp, 0.001));
}
// ── pose paths ─────────────────────────────────────────────────────────────
// blend two joint positions (a pose is just a set of points — lerp each)
fn mod_a3_mix(a: vec3f, b: vec3f, t: f32) -> vec3f { return mix(a, b, smoothstep(0.0, 1.0, t)); }
// quadratic bezier through a lifted midpoint — a reach, a leap, a nod
fn mod_a3_arc(a: vec3f, peak: vec3f, b: vec3f, t: f32) -> vec3f {
let u = 1.0 - t;
return a * u * u + peak * 2.0 * u * t + b * t * t;
}
// ── aim frames ─────────────────────────────────────────────────────────────
// build a basis aimed along `fw` (head look-at, torso facing); transform a
// local point into it. worldUp guards collinearity.
fn mod_a3_aim(local: vec3f, origin: vec3f, fw0: vec3f) -> vec3f {
let fw = normalize(fw0);
var up = vec3f(0.0, 1.0, 0.0);
if (abs(dot(fw, up)) > 0.99) { up = vec3f(0.0, 0.0, 1.0); }
let rt = normalize(cross(up, fw));
let u2 = cross(fw, rt);
return origin + rt * local.x + u2 * local.y + fw * local.z;
}
// ── a whole walker, as the pattern to copy ─────────────────────────────────
// Biped legs in one call: body at `hips` heading +z in its local frame,
// phase in stride cycles, stride length L. Returns the SDF of both legs.
// (Offset the two feet half a cycle; IK bends knees toward the pole.)
fn mod_a3_legs(p: vec3f, hips: vec3f, phase: f32, L: f32, legLen: f32, r: f32) -> f32 {
var d = 1e9;
for (var s = 0; s < 2; s++) {
let side = select(-1.0, 1.0, s == 1);
let g = mod_a3_gait(phase + select(0.0, 0.5, s == 1), 0.55);
let hip = hips + vec3f(side * legLen * 0.22, 0.0, 0.0);
let foot = vec3f(hip.x, g.y * legLen * 0.22, g.x * L);
let knee = mod_a3_ik2(hip, foot, legLen * 0.52, legLen * 0.52, hip + vec3f(0.0, 0.0, legLen));
d = min(d, mod_a3_bone(p, hip, knee, r, r * 0.75));
d = min(d, mod_a3_bone(p, knee, foot, r * 0.75, r * 0.5));
d = min(d, mod_a3_joint(p, knee, r * 0.85));
}
return d;
}
module · rooms
// VEILFIRE · rooms.wgsl — the load-bearing scene SDF (vf-rooms node).
// EXPORT: fn w3_map(p: vec3f) -> vec2f = (signed distance, material id) — a MORPH
// wrapper over w3_map_core; while ix-tween u176 is hot the old scene melds into the new.
//
// A WALKABLE Antichamber-style Gothic interior, carved SUBTRACTIVELY out of a
// solid rock block so rooms connect by construction (no coordinate seams):
// d = solidBlock; d = max(d, -naveAir); d = max(d, -sideAir); d = max(d, -door);
// then solids (columns, dais) are UNIONed back in with min(). Positive in open
// air, 0 at surfaces, negative inside rock — raymarchable from inside.
//
// ── ROOM EXTENTS (world units, y=0 is the floor) — movement/vf-* MIRROR THIS ──
// NAVE (main hall): x∈[-4, 4] y∈[0, 9] z∈[-9, 9] (tall)
// SIDE CHAMBER: x∈[ 5,11] y∈[0, 5] z∈[-3.5, 3.5] (lower ceiling)
// DIVIDING WALL: x∈[ 4, 5] (1 thick) — solid except the doorway
// DOORWAY (lancet): center x≈4.5, z=0, base y=0; opening z∈[-1.5,1.5],
// height y∈[0, 3.7] (straight to 2.5, point to 3.7),
// depth x∈[3.3, 5.7] (cuts the whole dividing wall)
// COLONNADE: two rows at x=±3.2, columns every 4 in z (z=0,±4,±8),
// r=0.4, floor→near ceiling (y∈[-0.5, 8.5])
// RISEN NAVE (warp≥0, past the corridor): a GRAND OPEN GOTHIC AVENUE — air box
// x∈[-12,12] y∈[0,17] z∈[-96,-54]; grown colonnades at |x|=9 + a crow-step
// GABLE facade closing the far end (z≈-96). mod_cath(p) (cathedral.wgsl) is
// unioned in this region; it GROWS via uni(48) 0→1. The reward chamber mouth
// (z=-56) opens into it. mats: 10 risen stone (walls/floor/arcade), 11 gable.
// ROOM A (warp≥0): x∈[-4,4] z∈[-24,-12.5] y∈[0,5] (warm colonnade, low)
// ROOM B (warp<0): x∈[-4,4] z∈[-30,-12.5] y∈[0,8] (grand vault — DEEPER
// than A and TALLER; the depth+height asymmetry IS the
// impossibility. Altar-key shrine at the far end z=-27.)
// DAIS (verticality, far +z end of nave):
// step1: x∈[-3.5,3.5] y∈[0,1] z∈[6,9] (low platform)
// step2: x∈[-3.5,3.5] y∈[0,2] z∈[7.8,9.2] (upper ledge)
// Suggested spawn: (0, 1.6, 0) facing +x toward the doorway, or -z down nave.
//
// MATERIAL IDS: 1 wall · 2 floor/dais/shrine · 3 column · 4 doorway/arch trim ·
// 5 manifold orb · 6 warren stone · 7 warren Room A pillars · 8 warren Room B altar
// · 9 THE LAIR (Room A gone wrong — same geometry as A, distinct mat so the
// renderer can make it near-dark/oppressive) · 10 RISEN NAVE stone (avenue
// walls/floor/vault + grown colonnades) · 11 gable/facade (the far-end crow-step
// gable + spire of the grown cathedral).
// Cheap: a handful of box/cyl/lancet evals, one z-repeat, no loops.
// per-RAY warren state, written by visual_s3 before each march (the cursed wing
// reads it to render two rooms flanking one column simultaneously).
// 0 = split · +1 Room A · −1 Room B · +2 THE LAIR (shares Room A's volume).
var<private> vf_warp: f32 = 0.0;
// ── THE WARREN (cursed wing, z < -9) — impossible geometry ───────────────────
// The nave's -z wall (z=-9) is pierced by a GRAND archway (x∈[-2.8,2.8], y to
// ~4.2). Immediately beyond is a short OPEN threshold, then a single COLUMN
// (floor→ceiling) at z=-12.5 on x=0. Behind the column TWO rooms share the
// SAME footprint but DIFFER in depth+height (the impossibility): Room A a warm
// pillared colonnade (z∈[-24,-12.5], y to 5); Room B a cool GRAND VAULT that
// runs DEEPER (z∈[-30,-12.5]) and TALLER (y to 8), with an altar-key SHRINE at
// the far end z=-27. Which one a ray sees is chosen by vf_warp — set per-RAY at
// the column plane, so a player in the nave sees BOTH flanking the column
// through the arch; latched once the player commits a side. Everything is
// carved from ONE continuous air box so rays can NEVER tunnel, and NOTHING (no
// jamb, wall, or ceiling drop) sits between the arch and the rooms — the
// unobstructed sightline is the whole trick.
const WARREN_COLZ: f32 = -12.5; // column just past the arch → the reveal is immediate
const WARREN_COLR: f32 = 0.5;
const WARREN_NEARZ: f32 = -9.8; // warren air near face (inner side of the -z wall slab)
// ── THE ASYMMETRIC CORRIDOR (Room A / lair only) ─────────────────────────────
// A tube pierces Room A's far wall (z=-24, opening x∈[-1,1]) into a small
// reward chamber. Its LENGTH is direction-latched by movement on uni(46):
// ~10u going IN (short), ~28u coming BACK (2.8× longer) — the same opening is
// longer on return. movement remaps the player z at the fold so world pos and
// this geometry always agree; the SDF stays a plain straight tube either way.
// Barred by a mat-4 grill that withdraws upward as the lock opens (uni45 0→1).
// The reward orb (mat 8) at the chamber's far end is a SOLID collision body in
// movement — the player stops in front of it and can NEVER walk through/past it
// (that was the vanishing-orb bug: a non-solid orb slid behind the player).
const CORR_WALLZ: f32 = -24.0; // Room A far wall / door plane
const CORR_LEN_IN: f32 = 10.0; // short (going in) — fallback when uni46 unset
// ── THE RISEN NAVE (wave 4) — the key-door payoff ────────────────────────────
// Past the corridor's reward chamber (mouth at z=-56) the world OPENS into a
// grand Gothic AVENUE: one big carved air box (|x|≤12, y≤17, z∈[-96,-54]) with
// the GROWN cathedral (mod_cath, cathedral.wgsl) unioned in — pointed-arch
// colonnades at world |x|=9 both sides + a crow-step gable facade at z≈-96.
// Growth is live via uni(48) 0→1 (bays assemble as you approach). Gated to
// Room A / lair (vf_warp≥0) so it never touches the nave/Room B. Cathedral
// coords MATCH cathedral.wgsl EXACTLY (that module is the source of truth):
// avenue-local zl = -56 - p.z (0 near .. 40 far); gable plane z=-96.
const RISEN_Z0: f32 = -54.0; // avenue air near face (chamber mouth opens here)
const RISEN_CZ: f32 = -75.0; // avenue box centre z (air z∈[-96,-54])
const RISEN_CY: f32 = 8.5; // avenue box centre y (air y∈[0,17])
fn w3_map_core(p: vec3f) -> vec2f {
// --- solid rock; block EXTENDED down -z to enclose the WHOLE warren volume
// (Room B far wall at z=-30, and the long-return corridor+chamber to z≈-56)
// so nothing floats in a wall-less void. Block: x∈[-5,12] y∈[-1,10] z∈[-58,10]. ---
let d0 = mod_w3_box(p - vec3f(3.5, 4.5, -24.0), vec3f(8.5, 5.5, 34.0));
let nave = mod_w3_box(p - vec3f(0.0, 4.5, 0.0), vec3f(4.0, 4.5, 9.0));
let side = mod_w3_box(p - vec3f(8.0, 2.5, 0.0), vec3f(3.0, 2.5, 3.5));
// TIME CELLS escape hall: low narrow corridor off the side chamber far wall
// (air x∈[11,18] z∈[-1,1] y∈[0,2.4]); rock unioned around it, air carved through.
let tcHallBlock = mod_w3_box(p - vec3f(14.6, 1.4, 0.0), vec3f(3.7, 2.4, 2.0));
let tcHall = mod_w3_box(p - vec3f(14.5, 1.2, 0.0), vec3f(3.5, 1.2, 1.0));
let secBlock = mod_w3_box(p - vec3f(0.0, 3.0, 13.5), vec3f(5.0, 4.0, 5.0));
let secAir = mod_w3_box(p - vec3f(0.0, 3.0, 13.5), vec3f(4.0, 3.0, 4.0));
let secPortal = mod_w3_lancet(vec3f(p.x, p.y, p.z - 9.0), 1.6, 3.0, 1.6, 1.6); // SECRET ROOM
// VOID CHAMBER (Galen) — a sealed room far out in +z, reachable ONLY by the
// back-room puzzle portal. Not joined to any corridor. Floor at y=0.
let voidBlock = mod_w3_box(p - vec3f(0.0, 3.0, 40.0), vec3f(5.0, 4.5, 5.0));
let voidAir = mod_w3_box(p - vec3f(0.0, 3.0, 40.0), vec3f(4.0, 3.5, 4.0));
// THE LURKER DIMENSION (Galen) — the unreal's pocket universe; entered by the den's
// spiral stair, or by the unreal CLOSING OVER you (crystal recalled inside the den).
let ldimBlock = mod_w3_box(p - vec3f(0.0, 3.2, 62.0), vec3f(6.0, 4.4, 6.5));
let ldimAir = mod_w3_box(p - vec3f(0.0, 3.0, 62.0), vec3f(5.0, 3.4, 5.5));
// the stair column CONTINUES into the dimension — you arrive at its other end
let ldimCol = max(length(p.xz - vec2f(0.0, 60.0)) - 0.26, abs(p.y - 3.0) - 3.4);
// THE TOMB DIMENSION (Galen) — below-death pocket entered through the open sarcophagus.
let tombBlock = mod_w3_box(p - vec3f(0.0, 2.4, 97.0), vec3f(5.6, 3.4, 6.4));
let tombAir = mod_w3_box(p - vec3f(0.0, 2.2, 97.0), vec3f(4.6, 2.4, 5.4));
// THE WOVEN ROOM (Galen) — a chamber of pure dimension at the FAR END of the
// time-puzzle escape hall (the hall runs to x=18). Simple walk-in; shell = mat 18.
let wovBlock = mod_w3_box(p - vec3f(22.5, 2.5, 0.0), vec3f(5.0, 3.5, 4.0));
let wovAir = mod_w3_box(p - vec3f(22.5, 2.6, 0.0), vec3f(4.0, 3.0, 3.0));
let wovDoor = mod_w3_box(p - vec3f(18.2, 1.2, 0.0), vec3f(1.5, 1.4, 1.0));
// side-chamber doorway: lancet arch, profile in (z,y), depth along x
let qd = vec3f(p.z, p.y, p.x - 4.5);
let door = mod_w3_lancet(qd, 1.5, 2.5, 1.2, 1.2);
// WARREN AIR — one continuous open volume: the threshold + BOTH rooms are the
// SAME box in x, but its DEPTH and HEIGHT are chosen per-ray so Room A is a low
// shallow colonnade and Room B a TALL, DEEPER grand vault. Nothing stands
// between the arch and the rooms. The nave -z wall survives as an 0.8u slab
// (z∈[-9.8,-9]); the grand archway is cut through it.
let roomFarZ = select(-24.0, -30.0, vf_warp < 0.0); // Room A far wall / Room B is DEEPER
let hallH = select( 5.0, 8.0, vf_warp < 0.0); // Room A low / Room B tall vault
let wCenterZ = (WARREN_NEARZ + roomFarZ) * 0.5;
let wHalfZ = (WARREN_NEARZ - roomFarZ) * 0.5; // nearZ > farZ ⇒ positive
let warren = mod_w3_box(p - vec3f(0.0, hallH * 0.5, wCenterZ), vec3f(4.0, hallH * 0.5, wHalfZ));
// grand archway: lancet profile in (x,y), depth in z, cut through the -z wall
let qa = vec3f(p.x, p.y, p.z + 9.4);
let arch = mod_w3_lancet(qa, 2.8, 2.8, 1.4, 0.7);
var d = d0;
// extend the solid rock down -z to ENCLOSE the RISEN NAVE avenue (z∈[-98,-52],
// |x|≤10.5, y∈[-1,18]) so the grand avenue is carved from real stone, not void.
// (halfX narrowed 13.5→10.5 to match the tighter avenue.)
let risenBlock = mod_w3_box(p - vec3f(0.0, RISEN_CY, RISEN_CZ), vec3f(10.5, 9.5, 23.0));
d = min(d, risenBlock);
d = min(d, tcHallBlock); // enclose the escape hall in rock
d = min(d, secBlock);
d = min(d, voidBlock);
d = min(d, ldimBlock);
d = min(d, tombBlock);
d = min(d, wovBlock);
d = max(d, -nave);
d = max(d, -secAir);
d = max(d, -voidAir);
d = max(d, -ldimAir);
d = max(d, -tombAir);
d = min(d, ldimCol); // the arrival shaft stands in the pocket
// THE DEN INTERIOR (Galen: bigger inside + walkable spiral) — the big inside,
// entered by PORTAL at the den mouth; central column; 12 REAL steps helixing up
// (floorH mirrors them EXACTLY — you climb what you see).
let diBlock = mod_w3_box(p - vec3f(0.0, 4.6, 82.0), vec3f(7.0, 5.2, 7.0));
let diAir = mod_w3_box(p - vec3f(0.0, 4.4, 82.0), vec3f(5.8, 4.6, 5.4));
d = min(d, diBlock);
d = max(d, -diAir);
let diCol = max(length(p.xz - vec2f(0.0, 82.0)) - 0.5, abs(p.y - 4.4) - 4.8);
d = min(d, diCol);
var diStep = 1e5;
if (diBlock < 2.0) {
for (var dk = 0; dk < 8; dk++) {
let da = f32(dk) * 0.7853982;
let dc = vec3f(sin(da) * 2.3, f32(dk) * 0.30 + 0.12, 82.0 - cos(da) * 2.3);
diStep = min(diStep, mod_w3_box(p - dc, vec3f(0.85, 0.12, 0.85)));
}
d = min(d, diStep);
}
// THE WOVEN ROOM is a LIVING FLUID MEMBRANE (Galen: dynamic cells that flow and
// re-form — NOT discrete orbs). A domain-warped animated field bulges the shell
// into shifting cells/worms; the whole surface seethes, flows, and re-knits.
// Player-scoped on uni(1)=player x (only at the hall end). Cheap (~6 trig), march-safe.
if (uni(1) > 15.0 && p.x > 17.4 && p.x < 27.6 && abs(p.z) < 4.0 && p.y < 6.6) {
let t = uni(0);
let s = p * 2.7;
let gy = sin(s.x + t * 0.6) * cos(s.y - t * 0.5)
+ sin(s.y + t * 0.7) * cos(s.z + t * 0.4)
+ sin(s.z - t * 0.5) * cos(s.x + t * 0.6);
let shell = (abs(gy) - 0.55) / 4.6; // animated GYROID shell → approx SDF
d = min(d, max(shell, d - 0.62)) * 0.68; // a seething cell-crust on every surface
}
d = max(d, -wovAir);
d = max(d, -wovDoor);
d = max(d, -(secPortal + (1.0 - clamp(uni(144), 0.0, 1.0)) * 4.0)); // GRAPHICAL BARRIER: the secret-room lancet doorway DILATES open as the lantern reveal u144 grows 0->1 (sealed wall at 0)
d = max(d, -side);
d = max(d, -tcHall); // carve the escape corridor through the far wall
d = max(d, -door);
// FAKE WALL (Galen) — the side chamber's +x wall RENDERS SOLID (no visual carve
// here), but the base collision has a `hall` region at x=11 so you WALK STRAIGHT
// THROUGH it into the escape hall (tcHall, x11-18). A hidden passage: looks like a
// wall, has no clipping.
d = max(d, -warren);
d = max(d, -arch);
// APPROACH CORRIDOR (Room A / lair only): a STRAIGHT open tube from the Room-A
// door (z=-24) all the way to the risen avenue (near face z=-54) — CONTINUOUS,
// so opening the door REVEALS the growing cathedral glowing at the end and you
// walk straight in. (The old fold/chamber made the door read as a dead-end wall
// — the avenue was hidden behind solid rock until a blind teleport. Removed.)
if (vf_warp >= 0.0) {
let corr = mod_w3_box(p - vec3f(0.0, 2.0, -39.0), vec3f(2.6, 2.0, 15.2)); // x[-2.6,2.6] y[0,4] z[-54.2,-23.8] — WIDER: the walkable slot (|x|<=2.2 after body radius) matches the visible opening, so you can't catch the door's edge
d = max(d, -corr);
// THE RISEN NAVE — carve the grand avenue open volume; the corridor opens onto it.
// THE WING IS ITS OWN ROOM (Galen): both realities share the grand air box —
// the lair's crouch-ceiling no longer follows you through the gate. (The low
// ceiling was ray-cost cover for the DELETED labyrinth; the dragon branch
// skips mod_cath entirely, so grand height costs LESS here than the cathedral.)
let avHalfY = 8.5;
let avCenY = RISEN_CY;
let avenue = mod_w3_box(p - vec3f(0.0, avCenY, RISEN_CZ), vec3f(8.5, avHalfY, 21.0));
if (uni(191) > 0.5 && vf_warp < 1.5) { // RELICS COMBINED — cathedral reality ONLY (the walled gable opens to a lancet GATEWAY). In the dragon wing (vf_warp>1.5) the gable stays the arena exit — the city never overwrites it.
let gd = mod_w3_lancet(vec3f(p.x, p.y, -p.z - 96.0), 2.6, 5.0, 2.6, 1.5);
d = max(d, -gd);
}
d = max(d, -avenue);
}
// shell material: floor low, doorway/arch trim near the cuts, else wall
var mat = 1.0;
if (p.y < 0.3) { mat = 2.0; }
if (door < 0.35 && p.y < 3.9) { mat = 4.0; } // side-chamber doorway trim
if (p.z < -9.0) { mat = select(6.0, 9.0, vf_warp > 1.5 && p.z > -24.0); } // warren stone · LAIR shell = mat 9 — the flesh STOPS at the gate; the wing is its own room
if (p.z < RISEN_Z0) { mat = 10.0; } // RISEN NAVE shell (walls/floor/vault) = mat 10
if (arch < 0.4 && p.y < 4.3) { mat = 4.0; } // grand archway trim
if (p.x > 18.5 && p.x < 27.5 && abs(p.z) < 4.0) { mat = 18.0; } // THE WOVEN ROOM — pure weave
if (abs(p.x) < 5.6 && p.z > 56.0 && p.z < 68.0) { // THE LURKER DIMENSION
mat = select(18.0, 16.0, p.y < 0.4); // weave walls, chitin floor
}
if (abs(p.x) < 6.0 && p.z > 91.0 && p.z < 103.5) { // THE TOMB DIMENSION
mat = select(16.0, 9.0, p.y < 0.4);
}
if (abs(p.x) < 7.2 && p.z > 75.0 && p.z < 89.5) { // THE DEN INTERIOR
mat = 16.0;
if (diStep < 0.3) { mat = 9.0; } // ember steps
}
// THE RISEN NAVE cathedral — grown arcades + gable, unioned into the avenue.
// mod_cath (cathedral.wgsl) reads uni(48) growth (0 = nothing, 1 = full avenue),
// returns distance only; assign mat here by z band (gable at z≈-96 = mat 11,
// colonnades/walls = mat 10). Gated to the risen region + Room A/lair (cheap).
if (vf_warp >= 0.0 && p.z < -50.0) {
if (vf_warp > 1.5) {
// THE LAIR — the SAME avenue is a grotesque column-labyrinth (mod_maze), not a
// cathedral. Same space, two realities: arches if you came via Room A, a maze
// if via the lane. A lit doorway (mod_maze_door) marks the far exit.
// THE LABYRINTH IS DELETED (Galen Jul 31) — no mod_maze; the risen hall runs
// OPEN straight to the octagon arena. THIS is the choppiness fix (the noise
// bone-columns are gone from every march step).
let dd = mod_maze_door(p);
if (dd < d) { d = dd; mat = 13.0; } // EXIT DOORWAY — opening into the arena
// THE DODECA ARENA (Galen) — a truncated-dodecahedron boss chamber beyond the exit.
let arenaRock = mod_w3_box(p - vec3f(4.012, 5.5, -105.5), vec3f(13.0, 12.0, 13.0));
d = min(d, arenaRock); // enclose the arena in rock
let oct = mod_octroom(p - vec3f(4.012, 0.0, -106.0), 9.0, 8.5);
d = max(d, -oct); // carve the OCTAGONAL room air (8 walls, flat floor+ceiling)
let tun = mod_w3_box(p - vec3f(4.012, 1.7, -95.5), vec3f(1.7, 2.0, 4.5));
d = max(d, -tun); // short tunnel: exit door → arena
if (p.z < -95.0) { mat = 14.0; } // arena crystal shell
} else {
let dc = mod_cath(p);
if (dc < d) { d = dc; mat = select(10.0, 11.0, p.z < -92.0); }
// THE TRAITOR COLUMN — separate instance, own horror materials (16 body / 17 eye)
let dtr = mod_cath_traitor(p);
if (dtr < d) { d = dtr; mat = 16.0; }
let dte = mod_cath_traitor_eye(p);
if (dte < d) { d = dte; mat = 17.0; }
}
}
// --- solids unioned back in (unions are march-safe; gate by region) ---
// colonnade: nave only (z > -8.8)
if (p.z > -8.8) {
let cz = (fract(p.z / 4.0 + 0.5) - 0.5) * 4.0;
let colp = vec3f(abs(p.x) - 3.2, p.y - 4.0, cz);
let col = mod_w3_cyl(colp, 4.5, 0.4);
if (col < d) { d = col; mat = 3.0; }
// dais / altar steps at the far +z end
let step1 = mod_w3_box(p - vec3f(0.0, 0.5, 7.5), vec3f(3.5, 0.5, 1.5));
if (step1 < d) { d = step1; mat = 2.0; }
let step2 = mod_w3_box(p - vec3f(0.0, 1.0, 8.5), vec3f(3.5, 1.0, 0.7));
if (step2 < d) { d = step2; mat = 2.0; }
// MANIFOLD orb — heart of the nave; mat 5
let mani = mod_vf_manifold(p, vec3f(0.0, 3.2, 1.0), 1.7);
if (mani < d) { d = mani; mat = 5.0; }
} else {
// THE COLUMN framed by the arch — floor→ceiling, hides the room seam
let wcol = mod_w3_cyl(vec3f(p.x, p.y - 3.5, p.z - WARREN_COLZ), 3.6, WARREN_COLR);
if (wcol < d) { d = wcol; mat = 3.0; }
// TWO rooms in the SAME volume beyond the column, chosen per-ray by vf_warp
if (p.z < WARREN_COLZ) {
if (vf_warp < 0.0) {
// Room B — cool GRAND VAULT. At the far end (z=-27) a SHRINE: a stone
// pedestal (mat 2) with the glowing altar-key sphere (mat 8) floating
// above it. Once carried (uni44>=0.5) the KEY is gone but the shrine
// pedestal remains as the vault's terminus.
let ped = mod_w3_box(p - vec3f(0.0, 0.6, -27.0), vec3f(1.0, 0.6, 1.0));
if (ped < d) { d = ped; mat = 2.0; }
if (uni(44) < 0.5) {
// THE KEY, suspended inside a glowing OPEN CAGE (armillary — 3 rings, so
// you see the key THROUGH the gaps in an opaque raymarcher). mat 8 = the
// glowing orb-cage; mat 12 = the gold key. Both vanish once carried.
let ac = p - vec3f(0.0, 2.6, -27.0);
let ring1 = mod_w3_torus(ac, 1.05, 0.05);
let ring2 = mod_w3_torus(mod_w3_rotX(ac, 1.5708), 1.05, 0.05);
let ring3 = mod_w3_torus(mod_w3_rotZ(ac, 1.5708), 1.05, 0.05);
let cage = min(ring1, min(ring2, ring3));
if (cage < d) { d = cage; mat = 8.0; }
// the key turns slowly (uni(0) clock) so it reads as floating, alive
let ka = mod_w3_rotY(ac, uni(0) * 0.6);
var key = mod_w3_torus(ka - vec3f(0.0, 0.34, 0.0), 0.22, 0.055); // bow (ring)
key = min(key, mod_w3_cyl(ka - vec3f(0.0, -0.12, 0.0), 0.46, 0.05)); // shaft
key = min(key, mod_w3_box(ka - vec3f(0.14, -0.52, 0.0), vec3f(0.14, 0.05, 0.05))); // tooth
key = min(key, mod_w3_box(ka - vec3f(0.11, -0.42, 0.0), vec3f(0.11, 0.05, 0.05))); // tooth
if (key < d) { d = key; mat = 12.0; }
}
} else {
// Room A — warm colonnade: pillar rows at x=±2.4 every 3u in z (mat 7).
// LAIR (vf_warp>1.5) = the SAME pillars, mat 9. Bound to the rooms band so
// the repeat doesn't spawn pillars down the corridor (z<-24).
if (p.z > -22.5) { // was -24.2 — drop the pillar row that lands ON the door plane (z=-24); it framed and pinched the opening
let pz = (fract((p.z + 1.5) / 3.0) - 0.5) * 3.0;
let pil = mod_w3_cyl(vec3f(abs(p.x) - 2.4, p.y - 2.5, pz), 2.5, 0.35);
if (pil < d) { d = pil; mat = select(7.0, 9.0, vf_warp > 1.5); }
}
// THE LOCK — a mat-4 grill barring the WIDE corridor door at z=-24,
// opening x∈[-2.2,2.2]. Bars slide UP as uni45 (doorOpen) blends 0→1.
if (uni(45) < 0.98 && abs(p.x) < 2.6 && p.z > -24.6 && p.z < -23.4) { // bars span the FULL widened opening so a closed door still seals
let baseY = 3.2 * clamp(uni(45), 0.0, 1.0);
let bxr = (fract(p.x / 0.34) - 0.5) * 0.34;
let bar = mod_w3_box(vec3f(bxr, p.y - (baseY + 3.2) * 0.5, p.z + 24.0),
vec3f(0.045, max((3.2 - baseY) * 0.5, 0.001), 0.09));
if (bar < d) { d = bar; mat = 4.0; }
}
// (the old cramped reward orb is GONE — the corridor now opens onto THE
// RISEN NAVE grand avenue, unioned above; the cathedral IS the payoff.)
}
}
}
// NODE⋈NODE INTERACTION (ix layer): geometry brought by DECLARED fields, rendered
// only inside field overlaps — composed by the ix-engine node via u150+. No world
// coordinates here; inert while u150 == 0.
let ixg = mod_ix_geo(p);
if (ixg.x < d) { d = ixg.x; mat = ixg.y; }
// ix DOOR CARVE: an active crypt record punches its hallway door through the wall
d = max(d, -mod_ix_door(p));
// ACT II — THE CITY (Galen): the gate opens into a huge DOME with a procedural city
// you walk into; it grows as you go; no blocking; this is where Act I is left behind.
// u191 = the three relics combined. Bounded to z<-88 so it costs nothing elsewhere.
if (uni(191) > 0.5 && p.z < -88.0 && vf_warp < 1.5) { // CATHEDRAL reality ONLY — the city dome must NOT carve in the dragon wing (vf_warp>1.5), where z<-88 is the octagon ARENA. This is the "gate overwrites the dragon room" fix.
// APPROACH + COLUMN DELETE (Galen: kill the pier right in front of the gate) — a
// solid box carve at the gate mouth, above the floor so the ground stays.
let appr = mod_w3_box(p - vec3f(0.0, 3.6, -93.0), vec3f(4.2, 3.4, 8.5));
d = max(d, -appr);
// THE DOME — a big domed void beyond the gable, floor at y=0.
let CC = p - vec3f(0.0, 0.0, -118.0);
let air = max(length(CC) - 21.0, -p.y);
d = max(d, -air);
// PROCEDURAL TOWERS — 7u grid; cells sit off the x=0 axis so the centre street
// stays open (no blocking); hashed heights; only inside the dome; stone (mat 10).
let gx = (fract(p.x / 7.0) - 0.5) * 7.0;
let gz = (fract(p.z / 7.0) - 0.5) * 7.0;
let cell = floor(vec2f(p.x, p.z) / 7.0);
let hh = 3.0 + fract(sin(dot(cell, vec2f(12.9898, 78.233))) * 43758.5) * 10.0;
let tower = max(mod_w3_box(vec3f(gx, p.y - hh * 0.5, gz), vec3f(1.8, hh * 0.5, 1.8)), length(CC) - 20.0);
if (tower < d) { d = tower; mat = 10.0; }
// NCX — THE CITY CROSSING: interceptors + THE EMBER BALL (vf-city-crossing hook;
// mats 20-23 shaded in s3). Self-gates on uni(83) — inert until the night runs.
let ncx = mod_ncx_geo(p);
if (ncx.x < d) { d = ncx.x; mat = ncx.y; }
}
return vec2f(d, mat);
}
// ── THE REALITY MORPH (Galen: "objects morph across the screen to find their
// nearest like thing and meld into it — scene to scene, like the tideglass
// ocean turning to fire"). While ix-tween's scalar (u176) decays after a
// teleport, the world marches a BLEND of two distance fields: the OLD scene's
// forms (space-shifted by the frozen jump offset u177-179 so they stand where
// you now stand) melting into the NEW scene's. Distance-field blending merges
// nearest surfaces into each other by construction — pillars flow into
// pillars, floors stay floors. A convex mix of two 1-Lipschitz fields is
// 1-Lipschitz: march-safe, no tunneling. Cost: 2nd core eval ONLY mid-tween.
fn w3_map(p: vec3f) -> vec2f {
let e = uni(176);
if (e <= 0.003) { return w3_map_core(p); }
let a = w3_map_core(p + vec3f(uni(173), uni(174), uni(175))); // offset moved off the sarco/tomb lane // the old scene, brought here
let b = w3_map_core(p); // the new scene
return vec2f(mix(b.x, a.x, e), select(b.y, a.y, e > 0.5));
}
module · demons
// VEILFIRE — DEMONS (composer node: vf-demons). The demon is now a GRAPH of
// part-nodes, each its own versioned module with its own eye:
// dm-legs · dm-body · dm-head (face+design) · dm-arms (+attack) · dm-skin (textures)
// This file only holds the shared smooth-union and the top composition contract.
//
// { "type": "define_module", "name": "demons", "wgsl": <this file> }
//
// EXPORT: fn vf_demon(p: vec3f, phase: f32, atk: f32) -> f32
// p : local body space (heading +z, feet near y=0, center at origin)
// phase : stride-cycle phase (population slot pop(2i+1).y)
// atk : attack blend 0..1 (population slot pop(2i+1).w) — drives dm-arms lunge
// Depends on dmLegs/dmBody/dmHead/dmArms/dmSkin (+ world3/anim3 beneath them).
// fleshy smooth-union — organic joints without gaps; k small = tight seam.
// Lives here (not in a part) so every part-module can call it.
fn vf_smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// COARSE field for the per-pixel MARCH: the full body minus the skin
// displacement. vf_dm_skin is an fbm (~40 hashes/eval) that wrinkles the
// surface by ±~0.03 — the march doesn't need that; re-evaluating it at
// every step was the single hottest cost on any demon-covered pixel
// (perf audit #1, Jul 28). The full skinned field is used only in the
// short refine at the hit + the normal taps, so shading is unchanged.
fn vf_demon_c(p: vec3f, phase: f32, atk: f32) -> f32 {
var d = vf_dm_legs(p, phase); // undercarriage
let trunk = vf_dm_body(p, phase); // spine + ribcage
let head = vf_dm_head(p, phase); // skull / face
var upper = vf_smin(trunk, head, 0.06); // smooth neck → skull
upper = vf_smin(upper, vf_dm_arms(p, phase, atk), 0.04);
return min(d, upper);
}
fn vf_demon(p: vec3f, phase: f32, atk: f32) -> f32 {
return vf_demon_c(p, phase, atk) + vf_dm_skin(p, phase); // + organic hide displacement
}
module · enemies
// enemies (node: enemies) — shared enemy shading helper. The enemy BODY draw is
// routed through veilfire/demons.wgsl's vf_demon inside renderer-mega; this holds
// the damage-flash tint the renderer can blend by hp so hurt demons read hot.
fn s3_enemyTint(hp01: f32) -> vec3f {
return mix(vec3f(1.0, 0.28, 0.16), vec3f(0.11, 0.05, 0.045), clamp(hp01, 0.0, 1.0));
}
module · proj
// shooter3 / projectiles — thin glowing energy bolts (tracers)
// Cheap capsule SDF + emissive HDR color the renderer adds on hit-glow.
// Exports:
// s3_tracer(p, a, b) -> f32 thin bolt from a to b (radius ~0.06)
// s3_tracerColor(kind, life01) -> vec3f linear HDR emissive (3=player, 4=enemy)
// A thin glowing bolt: a capsule from a to b. Cheap — one clamp/dot/length.
fn s3_tracer(p: vec3f, a: vec3f, b: vec3f) -> f32 {
return mod_w3_capsule(p, a, b, 0.06);
}
// Emissive HDR color the renderer adds. Brightest when freshly fired (life01~1),
// fading toward death (life01~0). kind 3 = player (hot cyan-white), 4 = enemy (sick green).
fn s3_tracerColor(kind: f32, life01: f32) -> vec3f {
let l = clamp(life01, 0.0, 1.0);
// WEAPON 3 (blue plasma) — narrow kind band 3.3 from the pickup weapon; deep
// electric blue, distinct from the cyan gun. (It can hurt you too — hook-side.)
if (kind > 3.15 && kind < 3.45) {
let bTail = vec3f(0.14, 0.34, 1.0);
let bCore = vec3f(0.72, 0.9, 1.0);
return mix(bTail, bCore, l) * mix(1.5, 11.0, l * l);
}
// hot core (near-white at spawn) blended toward the tinted tail as it ages
let playerHot = vec3f(0.55, 1.0, 1.0); // cyan
let playerCore = vec3f(1.0, 1.0, 1.0); // white-hot
let enemyHot = vec3f(0.35, 1.0, 0.15); // sick green
let enemyCore = vec3f(0.85, 1.0, 0.6); // pale green-white
let isEnemy = step(3.5, kind); // 0 for kind<=3, 1 for kind>=4
let hot = mix(playerHot, enemyHot, isEnemy);
let core = mix(playerCore, enemyCore, isEnemy);
// core dominates when fresh; tint takes over as it fades
let tint = mix(hot, core, l);
// HDR emission: strong when fresh, glowing dim tail when old
let energy = mix(1.2, 9.0, l * l);
return tint * energy;
}
module · atmos
// VEILFIRE — atmosphere. Dread mood layered over a plainly-lit color.
// vf_atmos(c, pos, rd, t, time):
// c = lit color at the hit (linear HDR)
// pos = hit point in world space
// rd = ray direction (unit)
// t = hit distance (fog falloff)
// time = seconds
// returns the color, dread-soaked. Output stays LINEAR HDR — no tonemap here.
//
// Four cheap layers, in order:
// 1. cool desaturation with a warm firelight bias (drain life, keep the fire)
// 2. torch flicker — layered sin-noise multiplying the whole scene
// 3. depth fog toward near-black cold murk (dread deepens it)
// 4. faint drifting ember specks low in the frame
//
// Mood dials read from the whiteboard (see SPEC): uni(12)=dread01, uni(18)=fogDensity.
// Both degrade gracefully to sane defaults when unset (zero).
fn vf_hash11(n: f32) -> f32 { return fract(sin(n * 43758.5453) * 12345.6789); }
fn vf_atmos(c: vec3f, pos: vec3f, rd: vec3f, t: f32, time: f32) -> vec3f {
var col = c;
let dread = clamp(uni(12), 0.0, 1.0);
let fogD = max(uni(18), 0.0);
// ── 1. cool desaturation + warm firelight bias ─────────────────────────────
// Drain saturation toward grey (harder the more dread), then re-tint: warm
// ember in the lit/bright regions, cold blue in the shadows.
let lum = dot(col, vec3f(0.2126, 0.7152, 0.0722));
let desat = mix(col, vec3f(lum), 0.42 + 0.28 * dread);
let warm = vec3f(1.18, 0.60, 0.28);
let cool = vec3f(0.52, 0.66, 0.96);
let tint = mix(cool, warm, clamp(lum * 1.6, 0.0, 1.0));
col = desat * tint;
// ── 2. torch flicker — layered sin-noise, multiplies everything ────────────
let f1 = sin(time * 11.0) * 0.5 + sin(time * 17.3 + 1.7) * 0.3 + sin(time * 29.0) * 0.2;
let f2 = sin(time * 3.1 + 0.5);
let flick = 1.0 + 0.06 * f1 + 0.03 * f2; // roughly ±9% breathing
col = col * flick;
// ── 3. depth fog — a LIFTED cold haze (KINDLE murk). Fog toward near-black is
// invisible in a dark room; a lifted blue-grey veils distance so the air reads
// thick. Two bands: a near haze that lifts shadows + a far murk that swallows.
let density = fogD + 0.013 + 0.012 * dread; // PERMAFOG: denser murk — full by ~15u so the short view is a soft wall, not a hard cut
let fogAmt = 1.0 - exp(-t * t * density);
let haze = vec3f(0.011, 0.015, 0.028); // PERMAFOG: deeper near-black cold murk (KINDLE HDR — ember pops against the dark)
col = mix(col, haze, fogAmt);
col = col + haze * 0.35 * fogAmt; // scatter lift so it glows, not just dims
// ── 4. drifting ember specks, low in the frame ─────────────────────────────
// Parameterize by ray direction so motes float in view; drift upward in time.
let ep = vec2f(rd.x * 6.0, rd.y * 6.0 - time * 0.35);
var ember = 0.0;
for (var k = 0; k < 3; k = k + 1) {
let sc = 1.0 + f32(k) * 1.7;
let g = ep * sc + vec2f(f32(k) * 13.7, f32(k) * 7.3);
let cell = floor(g);
let fr = fract(g) - 0.5;
let h = vf_hash11(cell.x * 57.0 + cell.y * 131.0 + f32(k) * 3.0);
let on = step(0.92, h); // only a few cells host an ember
let jit = vec2f(vf_hash11(h * 21.0) - 0.5, vf_hash11(h * 47.0) - 0.5) * 0.6;
let d = length(fr - jit);
let mote = on * smoothstep(0.09, 0.0, d);
let tw = 0.5 + 0.5 * sin(time * (4.0 + h * 6.0) + h * 30.0); // twinkle
ember = ember + mote * tw;
}
// Sit near the low end (bottom of view) and glow warmer through the murk.
let lowBias = smoothstep(0.30, -0.45, rd.y);
let emberCol = vec3f(1.30, 0.52, 0.16);
col = col + emberCol * ember * lowBias * (0.35 + 0.55 * fogAmt);
return col;
}
module · hud
// shooter3 HUD — screen-space overlay for the horror-shooter. No raymarch.
// Exports s3_hud(uv, hp01, ammo, score, dread01) -> vec4f (rgb, alpha); alpha 0
// where nothing is drawn so the integrator can composite it over the world.
//
// uv arrives -1..1 with y DOWN — the engine convention (uv.y=-1 top, +1 bottom;
// see render-core "MATCH THE ENGINE … uv.y increases DOWNWARD"). That already
// matches char5x7 / printInt (p in [0,1]², y DOWN), so s = uv*0.5+0.5 with no
// flip renders glyphs upright. Palette is thin, cold-white ink
// over near-black, with a rising red dread wash so the frame turns ominous as
// dread01 → 1. Legible on black; restrained on a lit scene.
// ── glyph cell helpers (screen coords s: 0..1, y DOWN) ────────────────────
// draw one glyph inside a box; returns coverage 0..1
fn s3_boxChar(s: vec2f, x0: f32, x1: f32, y0: f32, y1: f32, code: i32) -> f32 {
if (s.x < x0 || s.x > x1 || s.y < y0 || s.y > y1) { return 0.0; }
let p = vec2f((s.x - x0) / (x1 - x0), (s.y - y0) / (y1 - y0));
return char5x7(p, code);
}
// draw a right-aligned integer inside a box
fn s3_boxInt(s: vec2f, x0: f32, x1: f32, y0: f32, y1: f32, value: f32, digits: i32) -> f32 {
if (s.x < x0 || s.x > x1 || s.y < y0 || s.y > y1) { return 0.0; }
let p = vec2f((s.x - x0) / (x1 - x0), (s.y - y0) / (y1 - y0));
return printInt(p, value, digits);
}
// one glyph within a monospace run: start x0, cell width cw, index j (0.8 fill)
fn s3_cellChar(s: vec2f, x0: f32, cw: f32, y0: f32, y1: f32, j: i32, code: i32) -> f32 {
let cx0 = x0 + f32(j) * cw;
return s3_boxChar(s, cx0, cx0 + cw * 0.82, y0, y1, code);
}
fn s3_hud(uv: vec2f, hp01: f32, ammo: f32, score: f32, dread01: f32) -> vec4f {
// screen coords, top-left origin, y DOWN — matches the glyph font + engine.
let s = vec2f(uv.x * 0.5 + 0.5, uv.y * 0.5 + 0.5);
// ── THREE RELIC GEMS (Galen) — right edge above ammo; fill as collected (u190
// bitmask). All three collected (u191) → they blaze gold. ──
{
let rmask = i32(uni(190) + 0.5);
let allR = uni(191) > 0.5;
for (var gi = 0; gi < 3; gi = gi + 1) {
let gc = vec2f(0.945, 0.60 + f32(gi) * 0.072);
let gp = (s - gc) * vec2f(2.4, 1.0);
let dia = abs(gp.x) + abs(gp.y);
let bit = i32(1) << u32(gi);
let has = (rmask & bit) != 0;
let pulse = 0.7 + 0.3 * sin(uni(0) * 3.0 + f32(gi) * 1.7);
if (dia < 0.050) {
var gc2 = select(vec3f(0.30, 0.38, 0.6) * 0.6, vec3f(0.7, 0.95, 1.7) * pulse, has);
if (allR) { gc2 = gc2 + vec3f(1.1, 0.9, 0.45) * pulse; }
// gsv placeholder removed
return vec4f(gc2, select(0.5, 1.0, has || allR));
} else if (dia < 0.060) {
return vec4f(vec3f(0.55, 0.72, 1.1) * (0.6 + 0.4 * f32(has)), 0.8);
}
}
}
// ── THE CITY CROSSING — hearts + wave + kills, top centre (u83 gates) ──
if (uni(83) > 0.5) {
let hearts = i32(uni(138) + 0.5);
for (var hi = 0; hi < 3; hi = hi + 1) {
let hc = vec2f(0.44 + f32(hi) * 0.06, 0.075);
let hp2 = (s - hc) * vec2f(2.4, 1.0);
let dia = abs(hp2.x) + abs(hp2.y);
let has = hi < hearts;
if (dia < 0.020) {
let hcol = select(vec3f(0.16, 0.03, 0.03), vec3f(1.6, 0.22, 0.14) * (0.8 + 0.2 * sin(uni(0) * 3.0)), has);
return vec4f(hcol, select(0.45, 0.95, has));
} else if (dia < 0.025) {
return vec4f(vec3f(0.9, 0.25, 0.18), 0.7);
}
}
let wk = uni(139);
let wv = floor(wk / 100.0);
let kl = wk - wv * 100.0;
let wcov = s3_boxInt(s, 0.330, 0.400, 0.058, 0.092, wv, 2); // WAVE — left of hearts
if (wcov > 0.0) { return vec4f(vec3f(0.85, 0.87, 0.95), wcov * 0.9); }
let kcov = s3_boxInt(s, 0.620, 0.690, 0.058, 0.092, kl, 2); // KILLS this wave — right
if (kcov > 0.0) { return vec4f(vec3f(1.2, 0.5, 0.3), kcov * 0.9); }
}
let hp = clamp(hp01, 0.0, 1.0);
let dr = clamp(dread01, 0.0, 1.0);
var col = vec3f(0.0);
var a = 0.0;
// ── dread wash: a red vignette that swells from the corners with dread01 ──
// (built in uv space so it is symmetric regardless of the glyph y-flip)
{
let edge = smoothstep(0.55, 1.25, length(uv)); // 0 centre → 1 corners
// a slow, uneasy pulse so high dread never sits perfectly still
let breath = 0.82 + 0.18 * sin(uni(0) * 2.1);
let vig = edge * dr * breath;
col = vec3f(0.42, 0.02, 0.015) * vig;
a = vig * 0.65;
}
// ── HEALTH BAR — bottom-left, red→green by hp01, dark empty track, frame ──
{
let x0 = 0.035; let x1 = 0.315; let y0 = 0.905; let y1 = 0.940;
if (s.x >= x0 && s.x <= x1 && s.y >= y0 && s.y <= y1) {
let bt = 0.0035; // border thickness
let border = s.x < x0 + bt || s.x > x1 - bt || s.y < y0 + bt || s.y > y1 - bt;
let fillX = x0 + (x1 - x0) * hp;
if (border) {
col = vec3f(0.55, 0.56, 0.62); a = 0.85;
} else if (s.x <= fillX) {
// red at 0 → amber mid → green at full; scale value so low HP reads hot
let hc = mix(vec3f(0.95, 0.09, 0.06), vec3f(0.16, 0.92, 0.26), smoothstep(0.0, 1.0, hp));
col = hc * (0.85 + 0.15 * hp); a = 0.92;
} else {
col = vec3f(0.05, 0.02, 0.02); a = 0.55; // depleted track
}
}
// "HP" label just above the bar
let ly0 = 0.868; let ly1 = 0.898;
let lc = max(s3_cellChar(s, 0.035, 0.024, ly0, ly1, 0, 72), // H
s3_cellChar(s, 0.035, 0.024, ly0, ly1, 1, 80)); // P
if (lc > 0.0) { col = vec3f(0.72, 0.74, 0.80); a = lc * 0.9; }
}
// ── AMMO — bottom-right, printInt (3 digits), with a small label ──
{
let x0 = 0.80; let x1 = 0.965; let y0 = 0.905; let y1 = 0.945;
let cov = s3_boxInt(s, x0, x1, y0, y1, ammo, 3);
if (cov > 0.0) { col = vec3f(0.86, 0.88, 0.94); a = cov * 0.95; }
// "AMMO" label above the count
let ly0 = 0.868; let ly1 = 0.898;
let lx0 = 0.80; let cw = 0.026;
var lab = s3_cellChar(s, lx0, cw, ly0, ly1, 0, 65); // A
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 1, 77)); // M
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 2, 77)); // M
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 3, 79)); // O
if (lab > 0.0) { col = vec3f(0.66, 0.68, 0.74); a = lab * 0.85; }
}
// ── SCORE — top-right, printInt (6 digits), with a label ──
{
let x0 = 0.74; let x1 = 0.965; let y0 = 0.070; let y1 = 0.110;
let cov = s3_boxInt(s, x0, x1, y0, y1, score, 6);
if (cov > 0.0) { col = vec3f(0.90, 0.90, 0.95); a = cov * 0.95; }
// "SCORE" label above the number
let ly0 = 0.032; let ly1 = 0.062;
let lx0 = 0.74; let cw = 0.026;
var lab = s3_cellChar(s, lx0, cw, ly0, ly1, 0, 83); // S
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 1, 67)); // C
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 2, 79)); // O
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 3, 82)); // R
lab = max(lab, s3_cellChar(s, lx0, cw, ly0, ly1, 4, 69)); // E
if (lab > 0.0) { col = vec3f(0.62, 0.63, 0.70); a = lab * 0.82; }
}
// ── DREAD METER — thin vertical bar, right edge, fills bottom→top ──
{
let x0 = 0.972; let x1 = 0.990; let y0 = 0.32; let y1 = 0.90;
if (s.x >= x0 && s.x <= x1 && s.y >= y0 && s.y <= y1) {
let fillTop = y1 - (y1 - y0) * dr; // fills upward with dread
if (s.y >= fillTop) {
let up = (y1 - s.y) / (y1 - y0); // 0 bottom → 1 top of bar
let hot = mix(vec3f(0.55, 0.05, 0.04), vec3f(1.0, 0.28, 0.12), up);
col = hot; a = 0.9;
} else {
col = vec3f(0.06, 0.02, 0.02); a = 0.5; // empty channel
}
}
}
// ── CROSSHAIR — thin center cross with a gap, cold-white, reddens w/ dread ──
{
let ax = abs(uv.x); let ay = abs(uv.y);
let th = 0.0038;
let inR = 0.013; let outR = 0.050;
let horiz = ay < th && ax > inR && ax < outR;
let vert = ax < th && ay > inR && ay < outR;
let dot = length(uv) < 0.0028;
if (horiz || vert || dot) {
col = mix(vec3f(0.92, 0.95, 1.0), vec3f(1.0, 0.18, 0.12), dr);
a = 0.9;
}
}
return vec4f(col, clamp(a, 0.0, 1.0));
}
module · manifold
// VEILFIRE — MANIFOLD object (v5, ORB CONTRACT v3 — swarm/SPEC.md is LAW).
// A morphing TPMS orb that is genuinely ALIVE and HOSTILE:
// COIL uni(20): contracts (R*(1-0.28*charge)) + lattice tightens + the surface
// WRITHES (displacement amplitude grows with charge — boiling flesh).
// LASH uni(23)+uni(41): a curved WHIP — the tendril bows out to the side/up
// (maximal mid-extension), CRACKS straight at full reach, and splays a
// 3-claw tip. Reads as a whip from every angle (fixes the head-on
// foreshortening of a straight tendril).
// FLOW uni(24): DISSOLVE — the body drains to NOTHING (no remnant: scale has
// no floor) while 4 blobs (positions uni(29..40), hook-owned momentum +
// curl advection) swarm the nave; then the body REGROWS at the NEW home.
// HOME uni(26..28): dynamic — THE WHOLE ORB RELOCATES between anchors. The
// passed `c` is only the fallback when the rows are unset (boot).
// uni(22) is lifecycle's death-fade — never read here.
// Early-outs remain TRUE LOWER BOUNDS in every state (Jul 24 tunneling lesson).
//
// EXPORT: mod_vf_manifold(pw, c, R) -> f32 (signed distance)
fn mf_field2(p: vec3f, m: f32) -> f32 {
let sx = sin(p.x); let cx = cos(p.x);
let sy = sin(p.y); let cy = cos(p.y);
let sz = sin(p.z); let cz = cos(p.z);
let G = sx * cy + sy * cz + sz * cx; // gyroid
let P = (cx + cy + cz) * 0.5; // schwarz-P
let D = (sx * sy * sz + sx * cy * cz + cx * sy * cz + cx * cy * sz) * 0.75; // diamond
let N = (3.0 * (cx + cy + cz) + 4.0 * cx * cy * cz) * 0.14; // neovius
let w0 = clamp(1.0 - abs(m - 0.0), 0.0, 1.0);
let w1 = clamp(1.0 - abs(m - 1.0), 0.0, 1.0);
let w2 = clamp(1.0 - abs(m - 2.0), 0.0, 1.0);
let w3 = clamp(1.0 - abs(m - 3.0), 0.0, 1.0);
return (G * w0 + P * w1 + D * w2 + N * w3) / max(w0 + w1 + w2 + w3, 0.001);
}
fn mf_smin(a: f32, b: f32, k: f32) -> f32 { // polynomial smooth-min (undershoot ≤ k/4)
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
fn mf_seg(p: vec3f, a: vec3f, b: vec3f) -> f32 { // distance to segment ab
let ab = b - a;
let h = clamp(dot(p - a, ab) / max(dot(ab, ab), 1e-5), 0.0, 1.0);
return length(p - a - ab * h);
}
fn mod_vf_manifold(pw: vec3f, c: vec3f, R: f32) -> f32 {
let charge = clamp(uni(20), 0.0, 1.0);
let lash = clamp(uni(23), 0.0, 1.0);
let flow = clamp(uni(24), 0.0, 1.0);
// dynamic home — the whole orb relocates; fall back to the call-site c at boot
let hraw = vec3f(uni(26), uni(27), uni(28));
let hm = select(c, hraw, length(hraw) > 0.5);
let p0 = pw - hm;
let pr = length(p0);
let pl = vec3f(uni(1), uni(2), uni(3));
let toC = pl - hm;
let pdist = length(toC);
let lashLen = max(lash * min(pdist - 0.4, 5.2), 0.0);
// ---- TRUE LOWER BOUND (body ∪ whip ∪ blobs); never a constant --------------
// +0.35 margin covers breathing (±3.7%) and the charge spikes (≤0.12 outward)
var bound = pr - (R + 0.35);
if (lash > 0.005) {
// the whip bows/sags ≤ ~2.2 off the straight chord — stay conservative
let tipB = hm + (toC / max(pdist, 0.001)) * lashLen;
bound = min(bound, 0.7 * (mf_seg(pw, hm, tipB) - 2.6));
}
if (flow > 0.005) {
for (var k = 0; k < 4; k = k + 1) {
let bp = vec3f(uni(29 + k * 3), uni(30 + k * 3), uni(31 + k * 3));
bound = min(bound, 0.7 * (length(pw - bp) - 0.75));
}
}
if (bound > 0.4) { return bound; }
// ---- player-perspective morph + slight autonomous life ----------------------
let t = uni(0);
let ang = atan2(toC.z, toC.x);
let ca = cos(ang + t * 0.05); let sa = sin(ang + t * 0.05);
let q = vec3f(ca * p0.x - sa * p0.z, p0.y, sa * p0.x + ca * p0.z);
let baseM = 1.5 + 1.5 * sin(ang * 1.5) + 0.22 * sin(t * 0.33);
let m = mix(baseM, 2.0, charge * 0.85);
let f = 1.4 + clamp((7.0 - pdist) * 0.16, 0.0, 1.3) + 0.10 * sin(t * 0.5) + charge * 1.4;
// ---- BODY — coil contraction, WRITHE, full drain (NO remnant) ---------------
var d = 1e5;
// BREATHING: the orb is never dead-still — radius swells/relaxes ±2.5% at rest
let breathe = 1.0 + 0.025 * sin(t * 0.8) + 0.012 * sin(t * 2.1);
let Rc = R * (1.0 - 0.28 * charge) * (1.0 - flow) * breathe; // drains to ZERO
if (Rc > 0.03) {
let val = mf_field2(q * f, m);
// WRITHE: always alive at a murmur, boiling flesh as it winds up
let writhe = mf_field2(q * 4.2 + vec3f(0.0, t * 1.7, t * 1.1), 1.0) * (0.022 + 0.075 * charge);
// SPIKES: as it charges, sharp thorns push OUT of the flesh (ridges of a
// high-frequency field, crawling slowly) — the silhouette turns hostile
let spike = max(mf_field2(q * 5.0 + vec3f(t * 0.25, 0.0, t * 0.31), 2.0) - 0.55, 0.0);
let surf = (abs(val) - 0.34) / f * 0.30 + writhe - spike * 0.16 * charge;
d = max(surf, pr - Rc - spike * 0.16 * charge); // spikes pierce the cap too
}
// ---- MORPH LASH — a curved WHIP that cracks straight ------------------------
if (lash > 0.005 && pdist > 0.6) {
let dir = toC / max(pdist, 0.001);
let sag = clamp(uni(42), 0.0, 1.0); // 1 = the DRAG BACK after the crack
let world_up = vec3f(0.0, 1.0, 0.0);
// retracting: the spent whip DROOPS — its tip drags low as it's hauled in
let tip = hm + dir * lashLen - world_up * (sag * (1.0 - lash) * 1.2);
// side/up bow: maximal mid-extension (lash*(1-lash)*4), gone at the crack;
// on the way back the bow flips DOWNWARD (a dead weight, not a strike)
let side = select(1.0, -1.0, uni(41) < 0.0);
let perp = normalize(cross(dir, world_up) + vec3f(1e-4, 0.0, 0.0));
let bow = lash * (1.0 - lash) * 4.0;
let upAmt = mix(1.1, -1.0, sag);
let ctrl = hm + dir * (lashLen * 0.45) + perp * (side * 1.5 * bow) + world_up * (upAmt * bow);
// capsule chain along the quadratic bezier, tapering 0.30 → 0.09
var ld = 1e5;
var prev = hm;
for (var k = 1; k <= 7; k = k + 1) {
let h = f32(k) / 7.0;
let bp = mix(mix(hm, ctrl, h), mix(ctrl, tip, h), h);
let r = mix(0.30, 0.09, h);
ld = min(ld, mf_seg(pw, prev, bp) - r);
prev = bp;
}
// 3-claw splayed tip once the whip is committed
if (lash > 0.5) {
let claw = (lash - 0.5) * 2.0;
for (var k = 0; k < 3; k = k + 1) {
let ca2 = f32(k - 1) * 0.9;
let cdir = normalize(dir + perp * ca2 * 0.45 + world_up * (0.25 - 0.35 * f32(k % 2)));
ld = min(ld, mf_seg(pw, tip, tip + cdir * 0.55 * claw) - 0.055);
}
}
// dress the whip in the orb's own flesh
let lval = mf_field2(pw * 3.0, m);
ld = ld + clamp(lval * 0.10, -0.05, 0.05);
d = mf_smin(d, 0.7 * ld, 0.22);
}
// ---- DISSOLVE blobs — hook-owned momentum, gooey merges ---------------------
if (flow > 0.005) {
for (var k = 0; k < 4; k = k + 1) {
let bp = vec3f(uni(29 + k * 3), uni(30 + k * 3), uni(31 + k * 3));
let bq = pw - bp;
let dr = length(bq);
if (dr < 1.7) {
let bval = mf_field2(bq * 2.6 + vec3f(0.0, t * 0.8, 0.0), m);
let bdisp = clamp(bval * 0.10, -0.06, 0.06);
d = mf_smin(d, 0.7 * (dr - 0.55 + bdisp), 0.20); // goo — blobs merge wetly
} else {
d = min(d, 0.7 * (dr - 0.75));
}
}
}
return d;
}
module · dmLegs
// VEILFIRE · demon NODE: legs (dm-legs). The IK biped undercarriage — planted-
// foot gait via anim3. EXPORT: fn vf_dm_legs(p: vec3f, phase: f32) -> f32
// { "type": "define_module", "name": "dmLegs", "wgsl": <this file> }
// Depends on anim3-lib (mod_a3_legs).
fn vf_dm_legs(p: vec3f, phase: f32) -> f32 {
let hipY = 0.88; // hip height — feet reach y≈0
let L = 0.5; // stride length
let legLen = 0.95; // thigh+shin reach
return mod_a3_legs(p, vec3f(0.0, hipY, 0.0), phase, L, legLen, 0.075);
}
module · dmBody
// VEILFIRE · demon NODE: body (dm-body). The hunched trunk — spine (pelvis→chest→
// neck), skeletal ribcage, sternum ridge. EXPORT: fn vf_dm_body(p, phase) -> f32
// { "type": "define_module", "name": "dmBody", "wgsl": <this file> }
// Depends on anim3-lib (mod_a3_bone) + vf_smin (from the demons composer module).
fn vf_dm_body(p: vec3f, phase: f32) -> f32 {
let hipY = 0.88;
let pelvis = vec3f(0.0, hipY, 0.0);
let chest = vec3f(0.0, hipY + 0.42, 0.10);
let neck = vec3f(0.0, hipY + 0.60, 0.16);
var body = mod_a3_bone(p, pelvis, chest, 0.11, 0.15); // gaunt ribcage mass
body = vf_smin(body, mod_a3_bone(p, chest, neck, 0.13, 0.06), 0.08); // taper to neck
// ribcage: thin bones across the hunched chest → skeletal, ember-cracked
for (var r = 0; r < 4; r++) {
let ry = hipY + 0.13 + f32(r) * 0.085;
let rz = 0.04 + f32(r) * 0.012;
body = min(body, mod_a3_bone(p, vec3f(-0.115, ry, rz), vec3f(0.115, ry, rz), 0.012, 0.012));
}
// sternum ridge down the ribs
body = min(body, mod_a3_bone(p, vec3f(0.0, hipY + 0.10, 0.05), vec3f(0.0, hipY + 0.42, 0.09), 0.016, 0.010));
return body;
}
module · dmHead
// VEILFIRE · demon NODE: head (dm-head) — the FACE + DESIGN. A snarling skull:
// cranium, jutting snarl, swept horns, heavy brow, cheekbones, jutting lower jaw,
// deep-sunken eye sockets, and a fanged maw. EXPORT: fn vf_dm_head(p, phase) -> f32
// { "type": "define_module", "name": "dmHead", "wgsl": <this file> }
// The socket centres are the contract dm-eyes (render) reads to place the eyes:
// headC = (0, 1.64 + breathe, 0.20); sockets at headC + (±0.058, 0.02, 0.11).
// Depends on anim3-lib (mod_a3_joint/bone) + world3-lib (taperStrut/sphere/box) + vf_smin.
fn vf_dm_head(p: vec3f, phase: f32) -> f32 {
let hipY = 0.88;
let breathe = sin(phase * 6.2831853) * 0.02; // menace sway
let headC = vec3f(0.0, hipY + 0.76, 0.20 + breathe);
var d = mod_a3_joint(p, headC, 0.14); // cranium
d = vf_smin(d, // jutting snarl
mod_a3_bone(p, headC + vec3f(0.0, -0.02, 0.05), headC + vec3f(0.0, -0.06, 0.16), 0.09, 0.03), 0.05);
// swept horns
for (var s = 0; s < 2; s++) {
let sx = select(-1.0, 1.0, s == 1);
d = min(d, mod_w3_taperStrut(p, headC + vec3f(sx * 0.08, 0.08, -0.02), headC + vec3f(sx * 0.16, 0.30, -0.18), 0.035, 0.004));
}
// skull anatomy: heavy brow, cheekbones, jutting lower jaw
d = vf_smin(d, mod_a3_bone(p, headC + vec3f(-0.10, 0.055, 0.09), headC + vec3f(0.10, 0.055, 0.09), 0.032, 0.032), 0.02); // brow
d = vf_smin(d, mod_a3_bone(p, headC + vec3f(-0.10, -0.01, 0.05), headC + vec3f(-0.03, -0.05, 0.12), 0.026, 0.018), 0.03); // L cheek
d = vf_smin(d, mod_a3_bone(p, headC + vec3f( 0.10, -0.01, 0.05), headC + vec3f( 0.03, -0.05, 0.12), 0.026, 0.018), 0.03); // R cheek
let jaw = headC + vec3f(0.0, -0.11, 0.10);
d = vf_smin(d, mod_a3_bone(p, jaw + vec3f(-0.085, 0.0, -0.01), jaw + vec3f(0.085, 0.0, -0.01), 0.03, 0.03), 0.03); // jaw
// deep sunken eye sockets (dm-eyes glow sits inside these)
d = max(d, -mod_w3_sphere(p - (headC + vec3f(-0.058, 0.02, 0.11)), 0.050));
d = max(d, -mod_w3_sphere(p - (headC + vec3f( 0.058, 0.02, 0.11)), 0.050));
// gaping fanged maw
let mawC = headC + vec3f(0.0, -0.065, 0.13);
d = max(d, -mod_w3_box(p - mawC, vec3f(0.075, 0.05, 0.07)));
for (var tf = 0; tf < 4; tf++) {
let fx = (f32(tf) - 1.5) * 0.042;
d = min(d, mod_w3_taperStrut(p, mawC + vec3f(fx, 0.048, 0.0), mawC + vec3f(fx, -0.02, 0.055), 0.013, 0.001)); // upper fang
d = min(d, mod_w3_taperStrut(p, mawC + vec3f(fx, -0.048, 0.0), mawC + vec3f(fx, 0.015, 0.055), 0.011, 0.001)); // lower fang
}
return d;
}
module · dmArms
// VEILFIRE · demon NODE: arms (dm-arms) + ATTACK ANIM. Long clawed arms that
// counter-swing to the gait, and — driven by `atk` (0..1 from the AI) — rear back
// and LUNGE forward into a downward swipe. EXPORT: fn vf_dm_arms(p, phase, atk) -> f32
// { "type": "define_module", "name": "dmArms", "wgsl": <this file> }
// Depends on anim3-lib (gait/ik2/bone/joint) + world3-lib (taperStrut).
fn vf_dm_arms(p: vec3f, phase: f32, atk: f32) -> f32 {
let hipY = 0.88;
// ATTACK in two readable phases: WIND-UP (atk 0..0.55) coils the arms back and
// UP as a telegraph; STRIKE (atk 0.55..0.9) thrusts them far forward and slashes
// DOWN, talons splayed. Distinct rest → coil → slash so the attack is legible.
let a = clamp(atk, 0.0, 1.0);
let wind = smoothstep(0.0, 0.55, a);
let strike = smoothstep(0.55, 0.90, a);
var d = 1e9;
for (var s = 0; s < 2; s++) {
let sx = select(-1.0, 1.0, s == 1);
let g = mod_a3_gait(phase + select(0.5, 0.0, s == 1), 0.55); // counter-swing at rest
let shoulder = vec3f(sx * 0.20, hipY + 0.48, 0.06);
let restZ = 0.16 - g.x * 0.55;
let handZ = restZ - wind * 0.30 + strike * 0.98; // rear back, then far forward
let handY = (hipY - 0.32) + wind * 0.46 - strike * 0.36; // raise (telegraph), then slash down
let hand = vec3f(sx * (0.17 + wind * 0.05 - strike * 0.06), handY, handZ);
let pole = shoulder + vec3f(sx * 0.35, -0.1, -0.35); // elbow back/out
let elbow = mod_a3_ik2(shoulder, hand, 0.44, 0.46, pole);
var arm = mod_a3_bone(p, shoulder, elbow, 0.06, 0.045);
arm = min(arm, mod_a3_bone(p, elbow, hand, 0.045, 0.028));
arm = min(arm, mod_a3_joint(p, elbow, 0.05));
// three talons — splay wide on the strike
for (var c = 0; c < 3; c++) {
let ca = (f32(c) - 1.0) * (0.12 + strike * 0.10);
let tip = hand + vec3f(sx * 0.03 + ca * 0.05, -0.14, 0.06 + ca * 0.02);
arm = min(arm, mod_w3_taperStrut(p, hand, tip, 0.022, 0.002));
}
d = min(d, arm);
}
return d;
}
module · dmSkin
// VEILFIRE · demon NODE: skin (dm-skin) — TEXTURES. An organic fbm displacement
// added to the composed body: a sinew band + a fine pore/scar band, so the ember
// shading reads as taut charred flesh over bone (never a gingham grid). Amplitude
// is kept small so the sphere-march stays stable.
// EXPORT: fn vf_dm_skin(p: vec3f, phase: f32) -> f32 (signed displacement)
// { "type": "define_module", "name": "dmSkin", "wgsl": <this file> }
fn vf_dm_skin(p: vec3f, phase: f32) -> f32 {
let veins = fbm3d(p * 11.0 + vec3f(0.0, phase, 0.0), 3);
let pores = fbm3d(p * 27.0, 2);
return (veins - 0.5) * 0.020 + (pores - 0.5) * 0.006;
}
module · amb
// VEILFIRE · AMBUSHER composer (node: amb-model). ASSEMBLED ARTIFACT.
// EXPORT: fn vf_amb(p: vec3f, phase: f32, burst: f32) -> f32
// p: local frame (heading +z, feet y=0, x=0 centred) · phase: skitter cycle
// burst: 0 = folded almost FLAT (a vertical chitin sliver that tucks behind a
// 0.35r Room-A pillar) → 1 = a wide, too-many-limbed POUNCE, low and forward.
//
// This is the ONLY file the cartridge loads (as module 'amb'), so it must be
// SELF-CONTAINED: the part function bodies below are INLINE COPIES of the
// versioned sources in this directory — the true source of truth is:
// amb-core.wgsl (vf_amb_core — folded thorax)
// amb-limbs.wgsl (vf_amb_limbs — too-many IK legs, telescoping unfold)
// amb-head.wgsl (vf_amb_head — eyeless sensory head + reflective plate)
// EDIT THE PARTS, then mirror the body here. Keep them byte-identical.
// Beneath: anim3-lib (ik2/gait/bone/joint), world3-lib (taperStrut/rbox),
// fbm3d (shader prelude). amb_smin lives here so every part can call it.
// chitin smooth-union — tight seams between plates.
fn amb_smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
// ── INLINE: amb-core ─────────────────────────────────────────────────────────
fn vf_amb_core(p: vec3f, phase: f32, burst: f32) -> f32 {
let b = clamp(burst, 0.0, 1.0);
let P0 = mix(vec3f(0.0, 0.20, 0.02), vec3f(0.0, 0.36, -0.40), b);
let P1 = mix(vec3f(0.0, 1.02, 0.02), vec3f(0.0, 0.70, 0.26), b);
let P2 = mix(vec3f(0.0, 1.78, 0.00), vec3f(0.0, 0.60, 0.85), b);
let writhe = sin(phase * 6.2831853) * mix(0.02, 0.06, b);
let Pm = P1 + vec3f(writhe, 0.0, 0.0);
let r0 = mix(0.10, 0.17, b);
let r1 = mix(0.12, 0.20, b);
let r2 = mix(0.07, 0.12, b);
var d = mod_a3_bone(p, P0, Pm, r0, r1);
d = amb_smin(d, mod_a3_bone(p, Pm, P2, r1, r2), 0.10);
let crestTop = P2 + vec3f(0.0, mix(0.16, 0.06, b), mix(-0.02, 0.12, b));
let fin = mod_w3_taperStrut(p, P1 + vec3f(0.0, 0.10, 0.0), crestTop, mix(0.02, 0.06, b), 0.01);
d = amb_smin(d, fin, 0.06);
return d;
}
// ── INLINE: amb-limbs ────────────────────────────────────────────────────────
fn vf_amb_limbs(p: vec3f, phase: f32, burst: f32) -> f32 {
let b = clamp(burst, 0.0, 1.0);
var shFold = array<vec3f,3>(vec3f(0.05, 1.62, 0.02), vec3f(0.05, 1.14, 0.02), vec3f(0.05, 0.66, 0.02));
var shPnc = array<vec3f,3>(vec3f(0.10, 0.62, 0.70), vec3f(0.13, 0.66, 0.26), vec3f(0.15, 0.56, -0.18));
var ftFold = array<vec3f,3>(vec3f(0.07, 1.86, 0.06), vec3f(0.07, 1.40, 0.06), vec3f(0.07, 0.92, 0.06));
var ftPnc = array<vec3f,3>(vec3f(0.72, 0.00, 1.28), vec3f(0.82, 0.00, 0.42), vec3f(0.60, 0.02, -0.56));
let LL = mix(0.20, 0.62, b);
let br0 = mix(0.038, 0.058, b);
let br1 = mix(0.026, 0.045, b);
var d = 1e9;
for (var s = 0; s < 2; s = s + 1) {
let sx = select(-1.0, 1.0, s == 1);
for (var k = 0; k < 3; k = k + 1) {
var sh = mix(shFold[k], shPnc[k], b);
var ft = mix(ftFold[k], ftPnc[k], b);
sh.x = sh.x * sx; ft.x = ft.x * sx;
let g = mod_a3_gait(phase + f32(k) * 0.37 + f32(s) * 0.5, 0.5);
ft = ft + vec3f(0.0, g.y * 0.10 * b, g.x * 0.13 * b);
let pole = sh + vec3f(sx * (0.06 + 0.55 * b), 0.20 + 0.35 * b, 0.0);
let knee = mod_a3_ik2(sh, ft, LL, LL, pole);
var limb = mod_a3_bone(p, sh, knee, br0, br1);
limb = min(limb, mod_a3_bone(p, knee, ft, br1, br1 * 0.5));
limb = min(limb, mod_a3_joint(p, knee, br1 * 1.1));
let tip = ft + vec3f(sx * 0.02, -0.16, 0.06) * (0.4 + b);
limb = min(limb, mod_w3_taperStrut(p, ft, tip, br1 * 0.7, 0.001));
d = min(d, limb);
}
}
return d;
}
// ── INLINE: amb-head ─────────────────────────────────────────────────────────
fn vf_amb_head(p: vec3f, phase: f32, burst: f32) -> f32 {
let b = clamp(burst, 0.0, 1.0);
let neck = mix(vec3f(0.0, 1.78, 0.0), vec3f(0.0, 0.60, 0.85), b);
let tip = mix(vec3f(0.0, 2.20, 0.0), vec3f(0.0, 0.55, 1.35), b);
var d = mod_a3_bone(p, neck, tip, 0.13, 0.065);
let quiver = sin(phase * 6.2831853 + 1.7) * 0.01;
let plate = mod_w3_rbox(p - (tip + vec3f(quiver, 0.0, 0.0)), vec3f(0.22, 0.045, 0.13), 0.03);
d = amb_smin(d, plate, 0.06);
return d;
}
// ── TOP COMPOSITION ──────────────────────────────────────────────────────────
// COARSE field for the per-pixel MARCH: body without the hide texture. The
// gyroid + 3-octave fbm (~24 hashes) displace the surface by ±~0.03 — far too
// fine to matter mid-march, yet they ran at EVERY step (perf audit #1, Jul 28;
// at close range an ambusher's bound covers half the screen). The full field
// below is used only for the hit refine + normals, so shading is unchanged.
fn vf_amb_c(p: vec3f, phase: f32, burst: f32) -> f32 {
let b = clamp(burst, 0.0, 1.0);
var d = vf_amb_core(p, phase, b);
d = amb_smin(d, vf_amb_head(p, phase, b), 0.07);
return min(d, vf_amb_limbs(p, phase, b));
}
fn vf_amb(p: vec3f, phase: f32, burst: f32) -> f32 {
let b = clamp(burst, 0.0, 1.0);
var d = vf_amb_c(p, phase, burst);
// HIDE TEXTURE — TPMS gyroid ridges + fine fbm; strongest when folded (looks
// like cold carved stone lurking) and stretches smooth as it bursts open.
let gy = sin(p.x * 13.0) * cos(p.y * 13.0)
+ sin(p.y * 13.0) * cos(p.z * 13.0)
+ sin(p.z * 13.0) * cos(p.x * 13.0);
let pores = fbm3d(p * 8.0 + vec3f(0.0, phase, 0.0), 3);
d = d + gy * 0.008 * (1.0 - 0.6 * b) + (pores - 0.5) * 0.014;
return d;
}
module · vf_dragon
// VEILFIRE · DRAGON HEADER — the RIG + shared SDF helpers. This file is the
// skeleton's single source of truth: every part module poses its flesh on the
// joints returned by vfd_rig(ph). Follows the demon part-node architecture
// (dmLegs/dmBody/dmHead/dmArms/dmSkin → composer), scaled to a boss.
//
// Local body space: +z = FORWARD (faces the arena entrance), y up, ground y=0.
// ph = CONTINUOUS time-based phase (seconds-scale accumulator, NOT 0..1 fract);
// parts derive their own frequencies (breath ~1.9 rad/s, sway ~0.7 rad/s).
// chg = fire wind-up 0..1 (jaw gape, throat glow). flare = wing spread 0..1
// (0 folded against the body · 1 mantled wide for the volley).
fn vfd_h(p: vec3f) -> f32 { return fract(sin(dot(p, vec3f(12.9898, 78.233, 37.719))) * 43758.5453); }
fn vfd_smin(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
fn vfd_cap(p: vec3f, a: vec3f, b: vec3f, r: f32) -> f32 {
let pa = p - a; let ba = b - a;
let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
// tapered capsule (round cone): radius r1 at a → r2 at b
fn vfd_cone(p: vec3f, a: vec3f, b: vec3f, r1: f32, r2: f32) -> f32 {
let ba = b - a; let l2 = max(dot(ba, ba), 1e-5);
let pa = p - a;
let hx = clamp(dot(pa, ba) / l2, 0.0, 1.0);
return length(pa - ba * hx) - mix(r1, r2, hx);
}
fn vfd_dot2(v: vec3f) -> f32 { return dot(v, v); }
// unsigned distance to a triangle (IQ) — membrane panels are these minus thickness
fn vfd_tri(p: vec3f, a: vec3f, b: vec3f, c: vec3f) -> f32 {
let ba = b - a; let pa = p - a;
let cb = c - b; let pb = p - b;
let ac = a - c; let pc = p - c;
let nor = cross(ba, ac);
let inside = sign(dot(cross(ba, nor), pa)) + sign(dot(cross(cb, nor), pb)) + sign(dot(cross(ac, nor), pc));
if (inside < 2.0) {
return sqrt(min(min(
vfd_dot2(ba * clamp(dot(ba, pa) / vfd_dot2(ba), 0.0, 1.0) - pa),
vfd_dot2(cb * clamp(dot(cb, pb) / vfd_dot2(cb), 0.0, 1.0) - pb)),
vfd_dot2(ac * clamp(dot(ac, pc) / vfd_dot2(ac), 0.0, 1.0) - pc)));
}
return sqrt(dot(nor, pa) * dot(nor, pa) / vfd_dot2(nor));
}
// ── THE RIG — canonical joints, posed by phase. Standing crouch, ~4.2u tall at
// the head, chest at player eye height band, tail curling LEFT around the
// floor (never through the back wall). ──
struct VfdRig {
pelvis: vec3f,
chest: vec3f,
neckBase: vec3f,
neck1: vec3f,
neck2: vec3f,
head: vec3f,
jawHinge: vec3f,
tailBase: vec3f,
wingRootL: vec3f,
wingRootR: vec3f,
shoulderL: vec3f,
shoulderR: vec3f,
hipL: vec3f,
hipR: vec3f,
breathe: f32,
sway: f32,
}
fn vfd_rig(ph: f32) -> VfdRig {
var R: VfdRig;
let br = sin(ph * 1.9) * 0.05; // slow chest breathing
let sw = sin(ph * 0.7); // lazy neck/head sway
R.breathe = br;
R.sway = sw;
R.pelvis = vec3f(0.0, 2.05 + br * 0.4, -1.55);
R.chest = vec3f(0.0, 2.35 + br, -0.15);
R.neckBase = vec3f(0.0, 2.62 + br, 0.50);
// POUR STANCE (uni(51) = charge/jaw): the neck arcs DOWN as the fire builds,
// bringing the maw toward the floor it is about to flood with magma.
let lower = clamp(uni(51), 0.0, 1.0);
R.neck1 = vec3f(sw * 0.10, 3.12 + br * 0.7 - lower * 0.35, 0.95 + lower * 0.10);
R.neck2 = vec3f(sw * 0.16, 3.72 + br * 0.5 - lower * 0.85, 1.30 + lower * 0.28);
R.head = vec3f(sw * 0.22, 4.00 + br * 0.5 - lower * 1.45, 1.60 + lower * 0.45);
R.jawHinge = R.head + vec3f(0.0, -0.14, 0.12);
R.tailBase = vec3f(0.0, 1.90 + br * 0.3, -2.45);
R.wingRootL = vec3f(-0.78, 2.80 + br, -0.40);
R.wingRootR = vec3f( 0.78, 2.80 + br, -0.40);
R.shoulderL = vec3f(-0.95, 2.25, 0.20);
R.shoulderR = vec3f( 0.95, 2.25, 0.20);
R.hipL = vec3f(-0.92, 1.98, -1.50);
R.hipR = vec3f( 0.92, 1.98, -1.50);
return R;
}
// eye sockets — the render layer glows these; i=0 → +x (right), 1 → −x (left)
fn vfd_eye(i: i32, ph: f32) -> vec3f {
let R = vfd_rig(ph);
let s = select(-1.0, 1.0, i == 0);
return R.head + vec3f(s * 0.17, 0.12, 0.30);
}
// ── PENTARCH, THE UNBOUND (Aug 5 rebuild — Galen: "abstract like the orb, joints
// but MORE dangerous"). The flesh is gone: obsidian plates held in dragon-formation
// by the fire that consumed it. Plates orbit the same vfd_rig joints; uni(57)
// (flinch) flares the whole formation APART, exposing the core. All v2 behavior
// channels (gait u59-62, airborne u58, jaw chg, wing flare) drive it unchanged. ──
fn vfd_box(p: vec3f, b: vec3f, r: f32) -> f32 {
let q = abs(p) - b;
return length(max(q, vec3f(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r;
}
fn vfd_rotY(v: vec3f, a: f32) -> vec3f {
let c = cos(a); let s = sin(a);
return vec3f(c * v.x + s * v.z, v.y, -s * v.x + c * v.z);
}
fn vfd_rotX(v: vec3f, a: f32) -> vec3f {
let c = cos(a); let s = sin(a);
return vec3f(v.x, c * v.y - s * v.z, s * v.y + c * v.z);
}
fn vfd_torus(p: vec3f, R: f32, r: f32) -> f32 {
let q = vec2f(length(p.xz) - R, p.y);
return length(q) - r;
}
// global plate separation: resting hover + FLINCH FLARE (the wound opens the body)
fn vfd_sep() -> f32 { return 0.12 + clamp(uni(57), 0.0, 1.0) * 0.42; }
// HEAD — crown plate + twin jaw wedges (the gape is chg) + cheek shards + horns.
// A spark burns in the maw and grows into a furnace as the charge winds up.
fn vfd_head_horn(p: vec3f, ph: f32) -> f32 {
let R = vfd_rig(ph);
let q0 = p - R.head;
let q = vec3f(abs(q0.x), q0.y, q0.z);
return vfd_cone(q, vec3f(0.14, 0.14, -0.05), vec3f(0.44, 0.58, -0.72), 0.06, 0.012);
}
fn vfd_head(p: vec3f, ph: f32, chg: f32) -> f32 {
let R = vfd_rig(ph);
let sep = vfd_sep();
let q = p - R.head;
var d = vfd_box(q - vec3f(0.0, 0.17 + sep * 0.5, 0.02), vec3f(0.20, 0.035, 0.30), 0.04);
let uj = vfd_rotX(q - vec3f(0.0, 0.03, 0.22), -chg * 0.45 - sep * 0.25);
d = min(d, vfd_box(uj - vec3f(0.0, 0.01, 0.12), vec3f(0.14, 0.03, 0.28), 0.035));
let lj = vfd_rotX(q - vec3f(0.0, -0.13, 0.18), chg * 0.62 + sep * 0.25);
d = min(d, vfd_box(lj - vec3f(0.0, -0.02, 0.12), vec3f(0.12, 0.025, 0.26), 0.03));
let ck = vec3f(abs(q.x), q.y, q.z);
d = min(d, vfd_box(ck - vec3f(0.20 + sep * 0.4, 0.0, 0.05), vec3f(0.03, 0.10, 0.16), 0.03));
d = min(d, vfd_head_horn(p, ph));
d = min(d, length(q - vec3f(0.0, -0.05, 0.18)) - (0.05 + chg * 0.11));
return d;
}
// TRUNK — the chest FURNACE + belly ember + throat line (mat 4, visible through
// every gap), rib plates orbiting the fire, dorsal shard-spikes, pelvis block,
// three broken neck rings the flame threads, five tail links curling + swaying.
fn vfd_trunk(p: vec3f, ph: f32) -> f32 {
let R = vfd_rig(ph);
let sep = vfd_sep();
var d = length(p - R.chest) - 0.42;
d = min(d, length(p - (R.pelvis + vec3f(0.0, 0.05, 0.0))) - 0.22);
d = min(d, length(p - R.neck2) - 0.10);
let push = sep + R.breathe * 0.8;
let c = p - R.chest;
let cm = vec3f(abs(c.x), c.y, c.z);
let p1 = vfd_rotY(cm, 0.55) - vec3f(0.52 + push, 0.05, -0.05);
var rib = vfd_box(p1, vec3f(0.035, 0.34, 0.42), 0.05);
let p2 = vfd_rotY(cm, 1.15) - vec3f(0.48 + push, -0.10, -0.05);
rib = min(rib, vfd_box(p2, vec3f(0.035, 0.26, 0.34), 0.05));
d = min(d, rib);
let s0 = mix(R.chest, R.pelvis, 0.15) + vec3f(0.0, 0.45 + sep * 0.5, 0.0);
d = min(d, vfd_cone(p, s0 + vec3f(0.0, -0.18, 0.0), s0 + vec3f(0.0, 0.22, -0.10), 0.07, 0.01));
let s1 = mix(R.chest, R.pelvis, 0.55) + vec3f(0.0, 0.40 + sep * 0.5, 0.0);
d = min(d, vfd_cone(p, s1 + vec3f(0.0, -0.18, 0.0), s1 + vec3f(0.0, 0.20, -0.12), 0.06, 0.01));
d = min(d, vfd_box(p - R.pelvis + vec3f(0.0, 0.0, sep * 0.4), vec3f(0.26, 0.20, 0.26), 0.06));
d = min(d, vfd_torus(vfd_rotX(p - R.neckBase, 1.10), 0.30 + sep * 0.6, 0.045));
d = min(d, vfd_torus(vfd_rotX(p - R.neck1, 1.20), 0.24 + sep * 0.6, 0.040));
d = min(d, vfd_torus(vfd_rotX(p - R.neck2, 1.30), 0.19 + sep * 0.6, 0.035));
var tl = d;
for (var i = 0; i < 5; i = i + 1) {
let fi = f32(i);
let ang = 0.35 + fi * 0.42 + R.sway * 0.18;
let rad = 0.55 + fi * 0.34;
let lp = R.tailBase + vec3f(-sin(ang) * rad, -fi * 0.16 + sin(ph * 0.9 + fi) * 0.05, -cos(ang) * rad * 0.75);
let sz = 0.16 - fi * 0.023;
tl = min(tl, vfd_box(vfd_rotY(p - lp, ang), vec3f(sz, sz * 0.7, sz * 1.5), 0.03));
}
return tl;
}
struct VfdWg {
root: vec3f, // humerus root (wing root on the rig — breathes)
elbow: vec3f,
wrist: vec3f,
t0: vec3f, t1: vec3f, t2: vec3f, t3: vec3f, // four finger tips, leading→trailing
hip: vec3f,
}
// Canonical wing joints for the RIGHT (positive-x) side, posed by flare.
fn vfd_wg_joints(ph: f32, flare: f32) -> VfdWg {
let R = vfd_rig(ph);
let s = smoothstep(0.0, 1.0, flare);
var W: VfdWg;
W.root = R.wingRootR;
W.hip = R.hipR;
W.elbow = mix(vec3f(1.35, 2.9, -0.9), vec3f(2.6, 4.12, -0.52), s);
W.wrist = mix(vec3f(1.5, 3.1, -1.8), vec3f(3.8, 4.6, -0.4), s);
// fingers: folded sweep back along the flank ↔ flared fan wide
W.t0 = mix(vec3f(1.55, 3.35, -2.6), vec3f(5.85, 6.16, 0.8), s);
W.t1 = mix(vec3f(1.58, 3.05, -2.7), vec3f(6.09, 5.32, -0.28), s);
W.t2 = mix(vec3f(1.52, 2.75, -2.8), vec3f(5.85, 4.12, -1.12), s);
W.t3 = mix(vec3f(1.44, 2.45, -2.9), vec3f(5.12, 3.04, -1.84), s);
// FLAP — full wing-beat aloft (uni(58) = airborne01 from vf-arena-dragon),
// a faint idle stir on the ground. Amplitude grows down the chain (tips travel).
let air = clamp(uni(58), 0.0, 1.0);
let flap = sin(ph * 6.5) * air + sin(ph * 1.3) * 0.05 * (1.0 - air);
W.elbow.y = W.elbow.y + flap * 0.30;
W.wrist.y = W.wrist.y + flap * 0.75;
W.t0.y = W.t0.y + flap * 1.35; W.t0.z = W.t0.z + flap * 0.22;
W.t1.y = W.t1.y + flap * 1.30; W.t1.z = W.t1.z + flap * 0.20;
W.t2.y = W.t2.y + flap * 1.25; W.t2.z = W.t2.z + flap * 0.18;
W.t3.y = W.t3.y + flap * 1.15; W.t3.z = W.t3.z + flap * 0.16;
return W;
}
// WING BONES — humerus → radius/ulna → four finger bones, + a tiny thumb claw at
// the wrist. Radii taper 0.10 → 0.04 down the chain. Mirrored via abs(x).
fn vfd_wings(p: vec3f, ph: f32, flare: f32) -> f32 {
let q = vec3f(abs(p.x), p.y, p.z);
let W = vfd_wg_joints(ph, flare);
var d = vfd_cone(q, W.root, W.elbow, 0.10, 0.08); // humerus
d = vfd_smin(d, vfd_cone(q, W.elbow, W.wrist, 0.08, 0.06), 0.06); // radius/ulna
d = vfd_smin(d, vfd_cone(q, W.wrist, W.t0, 0.06, 0.04), 0.05); // finger I
d = vfd_smin(d, vfd_cone(q, W.wrist, W.t1, 0.06, 0.04), 0.05); // finger II
d = vfd_smin(d, vfd_cone(q, W.wrist, W.t2, 0.06, 0.04), 0.05); // finger III
d = vfd_smin(d, vfd_cone(q, W.wrist, W.t3, 0.06, 0.04), 0.05); // finger IV
// tiny thumb claw hooking off the wing wrist
let clawTip = W.wrist + vec3f(0.16, -0.12, 0.22);
d = min(d, vfd_cone(q, W.wrist, clawTip, 0.05, 0.005));
return d;
}
// WING BLADES — the membrane is gone: four separated glass blades per wing, one
// per finger (wrist → tip, trailing corner dropped). Gaps between blades leak the
// room light; flinch (sep) widens the fan. Mirrored via abs(x).
fn vfd_membrane(p: vec3f, ph: f32, flare: f32, th: f32) -> f32 {
let q = vec3f(abs(p.x), p.y, p.z);
let W = vfd_wg_joints(ph, flare);
let sep = vfd_sep();
let drop = vec3f(0.0, -0.55 - sep, -0.35);
var d = vfd_tri(q, mix(W.wrist, W.t0, 0.15), W.t0, mix(W.wrist, W.t0, 0.5) + drop) - th;
d = min(d, vfd_tri(q, mix(W.wrist, W.t1, 0.15), W.t1, mix(W.wrist, W.t1, 0.5) + drop) - th);
d = min(d, vfd_tri(q, mix(W.wrist, W.t2, 0.15), W.t2, mix(W.wrist, W.t2, 0.5) + drop) - th);
d = min(d, vfd_tri(q, mix(W.wrist, W.t3, 0.15), W.t3, mix(W.wrist, W.t3, 0.5) + drop) - th);
return d;
}
// VEILFIRE · DRAGON LIMBS — four legs in a coiled quadruped crouch, feet PLANTED.
// Poses flesh on the rig's shoulders/hips (vfd_rig(ph)); two-bone IK chains bend
// the knees while the toes stay nailed to the SPEC floor positions. The crouch
// carries the silhouette — front legs braced forward, rear haunches gathered.
// Exports: vfd_limbs (leg meat + toes, hide) · vfd_claws (claw cones, bone).
// ── one two-bone leg: root(shoulder|hip) → knee → ankle, foot pad to the plant.
// poleZ steers the bend: -1 elbows the front legs back, +1 gathers the rear
// haunches forward. sway = R.sway*0.03 leans knee/ankle only; foot stays put.
fn vfd_lb_leg(p: vec3f, root: vec3f, foot: vec3f, side: f32, poleZ: f32, sway: f32) -> f32 {
// ankle rides just above the planted foot, nudged by the weight shift
let ankle = vec3f(foot.x + sway, foot.y + 0.36, foot.z);
let span = length(root - ankle);
let l1 = span * 0.56; // upper bone (femur/humerus)
let l2 = span * 0.53; // lower bone — slight overlap → real bend
let pole = vec3f(root.x + side * 0.85, (root.y + ankle.y) * 0.5, root.z + poleZ * 1.5);
var knee = mod_a3_ik2(root, ankle, l1, l2, pole);
knee.x += sway; // weight shift — knee only
let upper = vfd_cone(p, root, knee, 0.22, 0.16);
let lower = vfd_cone(p, knee, ankle, 0.16, 0.12);
var d = vfd_smin(upper, lower, 0.11);
// foot pad: ankle down to the exact planted point (heel anchored)
d = vfd_smin(d, vfd_cone(p, ankle, foot, 0.12, 0.09), 0.07);
return d;
}
// ── three short toes splayed forward from a planted foot (feet DO NOT move).
fn vfd_lb_toes(p: vec3f, foot: vec3f, side: f32) -> f32 {
let base = foot + vec3f(0.0, 0.07, 0.02);
let t0 = foot + vec3f(-0.16 + side * 0.04, -0.02, 0.20);
let t1 = foot + vec3f( 0.00 + side * 0.06, -0.02, 0.26);
let t2 = foot + vec3f( 0.16 + side * 0.04, -0.02, 0.20);
var d = vfd_cone(p, base, t0, 0.08, 0.04);
d = vfd_smin(d, vfd_cone(p, base, t1, 0.08, 0.04), 0.05);
d = vfd_smin(d, vfd_cone(p, base, t2, 0.08, 0.04), 0.05);
return d;
}
// ── a claw hooking down+forward off each planted toe tip (bone-coloured).
fn vfd_lb_claws(p: vec3f, foot: vec3f, side: f32) -> f32 {
let t0 = foot + vec3f(-0.16 + side * 0.04, -0.02, 0.20);
let t1 = foot + vec3f( 0.00 + side * 0.06, -0.02, 0.26);
let t2 = foot + vec3f( 0.16 + side * 0.04, -0.02, 0.20);
let c0 = t0 + vec3f(-0.02, -0.05, 0.11);
let c1 = t1 + vec3f( 0.00, -0.06, 0.13);
let c2 = t2 + vec3f( 0.02, -0.05, 0.11);
var d = vfd_cone(p, t0, c0, 0.045, 0.008);
d = min(d, vfd_cone(p, t1, c1, 0.05, 0.008));
d = min(d, vfd_cone(p, t2, c2, 0.045, 0.008));
return d;
}
// ── GAIT — trot cycle on the planted feet. u59 walk01 gates it (0 = planted,
// exactly the old statue feet), u60 = stride phase, u61/u62 = unit motion dir
// in local space. Diagonal pairs (FR+RL / FL+RR) alternate; a foot lifts on
// its swing half and travels along the motion dir, knees/ankles follow by IK.
fn vfd_gait_off(off: f32) -> vec3f {
let wk = clamp(uni(59), 0.0, 1.0);
if (wk < 0.02) { return vec3f(0.0); }
let sp = uni(60) + off;
let along = cos(sp) * 0.5 * wk;
let lift = max(0.0, sin(sp)) * 0.24 * wk;
return vec3f(uni(61) * along, lift, uni(62) * along);
}
// EXPORT — the four legs + toes as one hide field.
fn vfd_limbs(p: vec3f, ph: f32) -> f32 {
let R = vfd_rig(ph);
let sway = R.sway * 0.03;
// feet: SPEC plants + the trot gait offsets (FR+RL swing together, FL+RR opposite)
let fL = vec3f(-1.35, 0.0, 0.65) + vfd_gait_off(3.14159);
let fR = vec3f( 1.35, 0.0, 0.65) + vfd_gait_off(0.0);
let rL = vec3f(-1.30, 0.0, -1.95) + vfd_gait_off(0.0);
let rR = vec3f( 1.30, 0.0, -1.95) + vfd_gait_off(3.14159);
var d = vfd_lb_leg(p, R.shoulderR, fR, 1.0, -1.0, sway); // front-right, elbow back
d = vfd_smin(d, vfd_lb_leg(p, R.shoulderL, fL, -1.0, -1.0, sway), 0.10);
d = vfd_smin(d, vfd_lb_leg(p, R.hipR, rR, 1.0, 1.0, sway), 0.10); // rear-right, haunch fwd
d = vfd_smin(d, vfd_lb_leg(p, R.hipL, rL, -1.0, 1.0, sway), 0.10);
d = vfd_smin(d, vfd_lb_toes(p, fR, 1.0), 0.05);
d = vfd_smin(d, vfd_lb_toes(p, fL, -1.0), 0.05);
d = vfd_smin(d, vfd_lb_toes(p, rR, 1.0), 0.05);
d = vfd_smin(d, vfd_lb_toes(p, rL, -1.0), 0.05);
return d;
}
// EXPORT — the claws only (mat classifier colours these as bone).
fn vfd_claws(p: vec3f, ph: f32) -> f32 {
// claws ride the SAME gait-offset feet as the legs — never detach mid-stride
let fL = vec3f(-1.35, 0.0, 0.65) + vfd_gait_off(3.14159);
let fR = vec3f( 1.35, 0.0, 0.65) + vfd_gait_off(0.0);
let rL = vec3f(-1.30, 0.0, -1.95) + vfd_gait_off(0.0);
let rR = vec3f( 1.30, 0.0, -1.95) + vfd_gait_off(3.14159);
var d = vfd_lb_claws(p, fR, 1.0);
d = min(d, vfd_lb_claws(p, fL, -1.0));
d = min(d, vfd_lb_claws(p, rR, 1.0));
d = min(d, vfd_lb_claws(p, rL, -1.0));
return d;
}
// VEILFIRE · DRAGON SKIN — fine-only signed displacement. The composer calls this
// once per pixel (for the normal), never on the coarse march. fbm3d is permitted
// HERE ONLY. Total amplitude stays ≤ 0.04 so the sphere-trace never overshoots.
//
// scale bands: (fbm3d(p*6.0,2) - 0.5) * 0.030 → coarse hide plating
// fine pores: (fbm3d(p*18.0,2) - 0.5) * 0.008 → skin grain
// belly smoother: scale amplitude * 0.4 where vfd_belly(p,ph) < 0.1
//
// Worst case: 0.030 + 0.008 = 0.038 ≤ 0.04 (belly reduction only lowers it).
// MICRO-FACETS — obsidian chips catch light; one trig product, no fbm.
fn vfd_skin(p: vec3f, ph: f32) -> f32 {
return sin(p.x * 23.0) * sin(p.y * 19.0) * sin(p.z * 21.0) * 0.004;
}
// VEILFIRE · DRAGON COMPOSER — same contract s3 calls; ALL hard mins now (the
// plates are meant to read as separate masses), which also marches cheaper.
fn vf_dragon_c(p: vec3f, ph: f32, chg: f32, flare: f32) -> f32 {
var d = vfd_head(p, ph, chg);
d = min(d, vfd_trunk(p, ph));
d = min(d, vfd_limbs(p, ph));
d = min(d, vfd_wings(p, ph, flare));
d = min(d, vfd_membrane(p, ph, flare, 0.06));
d = min(d, vfd_claws(p, ph));
return d;
}
fn vf_dragon(p: vec3f, ph: f32, chg: f32, flare: f32) -> f32 {
var d = vfd_head(p, ph, chg);
d = min(d, vfd_trunk(p, ph));
d = min(d, vfd_limbs(p, ph));
d = min(d, vfd_wings(p, ph, flare));
d = min(d, vfd_membrane(p, ph, flare, 0.028));
d = min(d, vfd_claws(p, ph));
return d + vfd_skin(p, ph);
}
// MAT — 0 obsidian plate · 2 wing blade · 3 horn/claw · 4 THE FIRE (chest furnace,
// belly ember, throat line, maw spark). Region 1 (belly) retired with the flesh.
fn vf_dragon_mat(p: vec3f, ph: f32, chg: f32, flare: f32) -> f32 {
let R = vfd_rig(ph);
var mat = 0.0;
if (vfd_membrane(p, ph, flare, 0.028) < 0.02) { mat = 2.0; }
if (vfd_head_horn(p, ph) < 0.02 || vfd_claws(p, ph) < 0.02) { mat = 3.0; }
let dCore = min(length(p - R.chest) - 0.42,
min(length(p - (R.pelvis + vec3f(0.0, 0.05, 0.0))) - 0.22,
length(p - R.neck2) - 0.10));
let maw = length(p - (R.head + vec3f(0.0, -0.05, 0.18))) - (0.05 + chg * 0.11);
if (min(dCore, maw) < 0.03) { mat = 4.0; }
return mat;
}
module · ixtint
// ixtint — ix FIELD-TINT effect class (Claude Opus). Reads owned lane u200-232 from vf-tomb-steam.
fn ix_tint(col: vec3f, p: vec3f, t: f32) -> vec3f {
if (uni(200) < 0.5) { return col; }
let expose = uni(201);
if (expose <= 0.003) { return col; }
let sc = vec3f(uni(202), uni(203), uni(204));
let sr = max(uni(205), 1e-3);
let bc = vec3f(uni(206), uni(207), uni(208));
let bh = vec3f(uni(209), uni(210), uni(211));
let inB = clamp(min(min(bh.x-abs(p.x-bc.x), bh.y-abs(p.y-bc.y)), bh.z-abs(p.z-bc.z))/0.25, 0.0, 1.0);
let inS = clamp((sr-length(p-sc))/max(sr*0.35, 0.1), 0.0, 1.0);
let mask = inB*inS*expose;
if (mask <= 0.0) { return col; }
let band = 0.5+0.5*sin(p.y*3.7-t*1.3 + sin(p.x*2.1+t*0.6)*1.5 + sin(p.z*2.4-t*0.4)*1.2);
let steam = vec3f(uni(212), uni(213), uni(214));
let k = mask*(0.55+0.45*band);
return mix(col, steam, clamp(k*0.85,0.0,1.0)) + steam*(k*0.12);
}
module · ncx
// NCX — THE CITY CROSSING (NOCTURNE DISTRICT mechanics ported into the Act II dome).
// Three classes of red interceptor run the centre street for the gate; THE EMBER BALL
// is the only thing that stops them. All geometry uniform-driven — zero baked coords.
// Lane: u83-90 ball · u91-99 ship pos · u133-139 boom+hud · u141-149 ship pose.
// Self-contained helpers (ncx_ prefix) — no cross-module deps (registration race).
// Ships are the Nocturne blade at 0.62 scale (their street was 8.5u; ours is 3.4u).
const NCX_SCL: f32 = 0.62;
fn ncx_rotY(p: vec3f, a: f32) -> vec3f {
let cc = cos(a); let ss = sin(a);
return vec3f(cc * p.x + ss * p.z, p.y, -ss * p.x + cc * p.z);
}
fn ncx_rotZ(p: vec3f, a: f32) -> vec3f {
let cc = cos(a); let ss = sin(a);
return vec3f(cc * p.x - ss * p.y, ss * p.x + cc * p.y, p.z);
}
// the interceptor blade (Nocturne hull, verbatim): sharp nose -z, raked tail
fn ncx_shipSdf(q0: vec3f) -> f32 {
let q = q0 / NCX_SCL;
let hull = (abs(q.x) * 1.5 + abs(q.y) * 2.6 + abs(q.z + 0.15) * 0.42) - 0.62;
var wq = q; wq.x = abs(wq.x);
let wing = max(max(wq.x - 1.5, abs(wq.y + 0.06 + wq.x * 0.10) - 0.045),
abs(wq.z - 0.45 - wq.x * 0.85) - max(0.5 - wq.x * 0.28, 0.05));
let fin = max(max(abs(q.x) - 0.05, abs(q.y - 0.35) - 0.34 + (q.z - 0.9) * 0.3), abs(q.z - 0.85) - 0.42);
return min(hull, min(wing, fin)) * NCX_SCL;
}
// ship-local frame for a world point (shading recomputes this in s3)
fn ncx_shipQ(p: vec3f, ei: i32) -> vec3f {
let e = vec3f(uni(91 + ei * 3), uni(92 + ei * 3), uni(93 + ei * 3));
return ncx_rotZ(ncx_rotY(p - e, uni(141 + ei * 3)), uni(142 + ei * 3));
}
// geometry entry — unioned into rooms w3_map inside the city gate. Returns (d, mat):
// mat 20/21/22 = interceptor by class (drifter/dodger/hunter) · 23 = THE EMBER BALL.
fn mod_ncx_geo(p: vec3f) -> vec2f {
if (uni(83) < 0.5) { return vec2f(1.0e5, 0.0); }
var d = 1.0e5; var m = 0.0;
for (var ei = 0; ei < 3; ei++) {
let ea = uni(92 + ei * 3);
if (ea < 0.01) { continue; }
let e = vec3f(uni(91 + ei * 3), ea, uni(93 + ei * 3));
let bb = length(p - e);
if (bb > 1.7) { // bound early-out: blade fits in r~1.3
if (bb - 1.35 < d) { d = bb - 1.35; }
continue;
}
let ds = ncx_shipSdf(ncx_shipQ(p, ei));
if (ds < d) { d = ds; m = 20.0 + floor(uni(143 + ei * 3)); }
}
let b = vec3f(uni(84), uni(85), uni(86));
let db = length(p - b) - 0.65;
if (db < d) { d = db; m = 23.0; }
return vec2f(d, m);
}
module · vf_lurker
// vf_lurker (node: vf-veil-lurker / vf-tomb render) — THE VEIL LURKER v2, an
// orb/dragon-grade creature module. Six-bladed void crawler; the head is now a
// SKULL with a pale FACE: carved eye sockets, brow ridge, cheek mass, and a
// HINGED MANDIBLE that gapes (gape 0..1, distance-keyed by the caller) over an
// ember mouth cavity. One truth: s3 marches vfl_c and shades from vfl_map's
// material id + vfl_head (the live head center, sway included).
fn vfl_seg(p: vec3f, a: vec3f, b2: vec3f) -> f32 {
let pa = p - a; let ba = b2 - a;
let h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-5), 0.0, 1.0);
return length(pa - ba * h);
}
fn vfl_sm(a: f32, b: f32, k: f32) -> f32 {
let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
fn vfl_head(ph: f32) -> vec3f {
let sway = vec3f(0.05 * sin(ph * 2.3), 0.05 * sin(ph * 3.1), 0.0);
return vec3f(0.0, 1.18 + 0.04 * sin(ph * 5.3), 0.30) + sway * 0.6;
}
// returns (dist, mat): 0 void body · 1 bone face · 3 mandible
fn vfl_map(p: vec3f, ph: f32, gape: f32) -> vec2f {
let sway = vec3f(0.05 * sin(ph * 2.3), 0.05 * sin(ph * 3.1), 0.0);
let th = vec3f(0.0, 1.75, 0.0) + sway;
// thorax + hunch hump, segment-ridged carapace (amp 0.018 — Lipschitz-safe)
var body = length((p - th) * vec3f(1.0, 1.25, 1.0)) - 0.34;
body = vfl_sm(body, length((p - (th + vec3f(0.0, 0.30, -0.28))) * vec3f(1.0, 1.15, 0.92)) - 0.24, 0.16);
body += 0.018 * sin(p.y * 26.0 + ph * 0.5);
// six blade legs + knuckle joints (unchanged silhouette from v1 — it still skitters)
let ang = atan2(p.x, p.z) + 0.5235988;
let sect = 1.04719755;
let k = floor(ang / sect + 0.5);
let af = ang - sect * k;
let r2 = length(p.xz);
let q = vec3f(sin(af) * r2, p.y, cos(af) * r2);
let jit = sin(ph * 9.0 + k * 2.7) * 0.14;
let hip = vec3f(0.0, 1.6, 0.28);
let knee = vec3f(0.0, 1.95 + jit * 0.5, 0.85);
let foot = vec3f(0.0, 0.0, 1.75 + jit);
var lg = min(vfl_seg(q, hip, knee), vfl_seg(q, knee, foot)) - 0.055;
lg = min(lg, length(q - knee) - 0.095);
body = min(body, lg);
let hd = vfl_head(ph);
// neck — thorax to skull
body = vfl_sm(body, vfl_seg(p, th + vec3f(0.0, -0.22, 0.10), hd + vec3f(0.0, 0.10, -0.08)) - 0.085, 0.11);
// SKULL: cranium + cheek/muzzle mass + brow ridge
var skull = length((p - hd) * vec3f(1.0, 0.94, 0.96)) - 0.335;
skull = vfl_sm(skull, length((p - (hd + vec3f(0.0, -0.08, 0.16))) * vec3f(1.22, 1.18, 1.0)) - 0.18, 0.09);
skull = vfl_sm(skull, vfl_seg(p, hd + vec3f(-0.175, 0.14, 0.22), hd + vec3f(0.175, 0.14, 0.22)) - 0.055, 0.07);
// carve the eye sockets
let socL = hd + vec3f(-0.125, 0.075, 0.26);
let socR = hd + vec3f( 0.125, 0.075, 0.26);
skull = max(skull, -(length(p - socL) - 0.10));
skull = max(skull, -(length(p - socR) - 0.10));
// carve the mouth cavity (the maw the mandible swings away from)
skull = max(skull, -(vfl_seg(p, hd + vec3f(-0.12, -0.075, 0.34), hd + vec3f(0.12, -0.075, 0.34)) - 0.14));
// MANDIBLE — hinged at the jaw root, swings down-forward with gape
let ga = 0.12 + clamp(gape, 0.0, 1.0) * 1.15;
let jr = hd + vec3f(0.0, -0.125, 0.05);
var jp = p - jr;
let cj = cos(ga); let sj = sin(ga);
jp = vec3f(jp.x, cj * jp.y + sj * jp.z, -sj * jp.y + cj * jp.z);
let jaw = length((jp - vec3f(0.0, -0.02, 0.34)) * vec3f(1.0, 1.7, 0.9)) - 0.17;
var d = body; var m = 0.0;
if (skull < d) { d = skull; m = 1.0; }
if (jaw < d) { d = jaw; m = 3.0; }
return vec2f(d, m);
}
fn vfl_c(p: vec3f, ph: f32, gape: f32) -> f32 { return vfl_map(p, ph, gape).x; }
module · crux
// CRUX — THE BURNING FIVE (kind 14): giant flaming crucifixes, the city's
// flying hunters. The megashader's s3 branch is a thin call into crux_draw
// (the 59KB preflight cap forced the body out here — modules compose free).
// Solid charred cross for the march; fire is shading — molten cracks + fbm
// flame advected upward + a flame veil — and crux_halo is the world-staining
// firelight, keyed to lanes u152-171 (x,y,z,intensity ×5) from vf-city-crucifix.
fn crux_box(p: vec3f, b: vec3f, r: f32) -> f32 {
let q = abs(p) - b;
return length(max(q, vec3f(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r;
}
// local-frame cross: vertical beam + crossbeam high on the shaft. ph = burn sway.
fn crux_sdf(p: vec3f, ph: f32) -> f32 {
var q = p;
q.x += sin(ph) * 0.06 * p.y * 0.18;
let v = crux_box(q, vec3f(0.42, 3.1, 0.30), 0.10);
let h = crux_box(q - vec3f(0.0, 1.35, 0.0), vec3f(2.05, 0.40, 0.30), 0.10);
return min(v, h);
}
// full draw: march + shade one crucifix. b = (hp01+hurt, phase, yaw, attack).
// returns vec4f(color, hitT) — hitT < 0.0 means miss (color = see-through veil).
fn crux_draw(ro: vec3f, rdF: vec3f, ppos: vec3f, b: vec4f, sh: f32, br: f32, nearT: f32, time: f32, uv: vec2f) -> vec4f {
let yaw = b.z;
let hurtC = max(0.0, b.x - 1.0);
let hpC = clamp(b.x, 0.0, 1.0);
let cyx = cos(-yaw); let syx = sin(-yaw);
let o0 = ro - ppos;
var lro = vec3f(cyx * o0.x + syx * o0.z, o0.y, -syx * o0.x + cyx * o0.z);
if (hurtC > 0.02) { lro = lro + vec3f(sin(time * 47.0 + uv.y * 90.0), sin(time * 53.0 + uv.x * 40.0), sin(time * 43.0 + uv.x * 70.0)) * 0.12 * hurtC; }
let lrd = vec3f(cyx * rdF.x + syx * rdF.z, rdF.y, -syx * rdF.x + cyx * rdF.z);
var t = max(sh, 0.02);
var hitT = -1.0;
for (var s = 0; s < 22; s = s + 1) {
let d = crux_sdf(lro + lrd * t, b.y);
if (d < 0.008) { hitT = t; break; }
t = t + max(d * 0.85, 0.012);
if (t > nearT || t > sh + 2.0 * br) { break; }
}
// FLAME VEIL — fire licks the air around the body, advected upward, dims as it dies
var veil = vec3f(0.0);
let vEnd = select(min(nearT, sh + 2.0 * br), hitT, hitT > 0.0);
let vT0 = max(sh, 0.02);
for (var s = 0; s < 3; s = s + 1) { // PERF: 5→3 taps (Galen lag report)
let tt = vT0 + (vEnd - vT0) * (f32(s) + 0.5) / 3.0;
let sp2 = lro + lrd * tt;
let sheath = smoothstep(0.85, 0.0, crux_sdf(sp2, b.y));
let fl = fbm3d(sp2 * vec3f(1.5, 0.9, 1.5) + vec3f(0.0, -time * 2.6, 0.0), 2); // PERF 3→2 oct
veil += vec3f(1.8, 0.55, 0.08) * sheath * max(fl, 0.0) * (0.50 + hpC * 0.75); // rescaled for 3 taps
}
if (hitT <= 0.0 || hitT >= nearT) { return vec4f(veil, -1.0); }
let lp = lro + lrd * hitT;
let ekc = vec2f(1.0, -1.0) * 0.014;
let nn = normalize(
ekc.xyy * crux_sdf(lp + ekc.xyy, b.y) + ekc.yyx * crux_sdf(lp + ekc.yyx, b.y) +
ekc.yxy * crux_sdf(lp + ekc.yxy, b.y) + ekc.xxx * crux_sdf(lp + ekc.xxx, b.y));
// charred black timber, cracked open with molten veins that breathe
let crack = pow(0.5 + 0.5 * fbm3d(lp * vec3f(2.2, 5.5, 2.2), 3), 3.0);
var cc = vec3f(0.035, 0.02, 0.016) * (0.25 + max(dot(nn, vec3f(0.0, 1.0, 0.0)), 0.0) * 0.4);
let breathe = 0.7 + 0.3 * sin(time * 3.1 + b.y * 2.0);
cc += vec3f(1.9, 0.30, 0.03) * crack * breathe * (0.5 + hpC);
// FIRE SKIN — flame climbing the beams, denser toward the crown
let fire = fbm3d(lp * vec3f(1.8, 1.1, 1.8) + vec3f(0.0, -time * 3.2, 0.0), 3);
let lick = smoothstep(0.1, 0.75, fire) * (0.45 + 0.55 * clamp(lp.y * 0.25 + 0.6, 0.0, 1.0));
cc += mix(vec3f(1.7, 0.35, 0.02), vec3f(2.1, 1.5, 0.35), pow(lick, 2.0)) * lick * (0.8 + hpC * 0.8);
// white-hot rim — the silhouette itself burns
let rimC = pow(clamp(1.0 + dot(nn, lrd), 0.0, 1.0), 2.0);
cc += vec3f(2.0, 1.0, 0.25) * rimC * (0.8 + 0.5 * sin(time * 7.0 + b.y * 3.0));
cc += vec3f(2.2, 0.8, 0.1) * b.w * 0.9; // dive blaze — flares as it drops on you
cc += vec3f(2.2, 2.0, 1.8) * hurtC * 0.9; // hurt flash (universal law)
return vec4f(cc + veil, hitT);
}
// world-staining firelight: 5 point-halos, closest ray approach clamped to the
// hit so the light never bleeds through walls. Call after the pop loop.
fn crux_halo(colIn: vec3f, ro: vec3f, rd: vec3f, nearT: f32, time: f32) -> vec3f {
var col = colIn;
for (var ci = 0; ci < 5; ci = ci + 1) {
let cw = uni(155 + ci * 4);
if (cw < 0.01) { continue; }
let cp = vec3f(uni(152 + ci * 4), uni(153 + ci * 4), uni(154 + ci * 4));
let ocx = cp - ro;
let tca = clamp(dot(ocx, rd), 0.0, nearT);
let dvx = ocx - rd * tca;
let dq = dot(dvx, dvx);
if (dq > 90.0) { continue; } // PERF: beyond ~9.5u off-ray the glow is invisible — skip the noise
let flick = 0.72 + 0.28 * sin(time * 11.0 + f32(ci) * 2.13) * sin(time * 5.7 + f32(ci));
let dens = 0.55 + 0.45 * fbm3d((ro + rd * tca) * 0.55 + vec3f(0.0, -time * 1.7, 0.0), 1);
let g = min(cw * flick * 2.0 / ((1.0 + 0.22 * dq) * (1.0 + 0.012 * tca * tca)), 1.35);
col += vec3f(1.9, 0.55, 0.10) * g * dens; // fire-textured, capped — no point-blank washout
}
return col;
}
— STEP HOOKS (JAVASCRIPT) —
hook · vf-player
by Claude (Fable · E)
;(() => {
// vf-player — PLAYER — WASD/mouse-look movement, jump/gravity, wall-slide, warren room-commit, LAIR one-way lane, first-person camera (u1-5,43,46,240-247)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = '4f162c44f9-wing1/' + globalThis.__VF_GEO_REV
if (__C['vf-player@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
function movement(sim, dt) {
const wd = sim.worldData
if (!wd.__vf) wd.__vf = {}
const V = wd.__vf
if (V.mv !== 2) { V.px = 0; V.py = 1.7; V.pz = -6; V.yaw = 0; V.pitch = 0; V.vy = 0; V.ground = 1; V.lx = null; V.ly = null; V.mv = 2 }
if (V.cL == null) { V.cL = CORR_LEN_IN; V.cfold = false; V.laneWas = false }
const step = Math.min(dt, 1 / 30)
const inp = wd.input || {}
const uu = Array.isArray(wd.gpuUniforms) ? wd.gpuUniforms : null
// DELETE THE WALL THE INSTANT THE KEY TURNS. Open on the LATCHED unlock flag
// (V.doorUnlocking, set by warren.mjs the frame the key unlocks the door) OR
// the ramping uni45. Reading the session flag directly removes ALL dependency
// on uniform propagation timing — no "looks open but still a wall" window. Once
// unlocked it stays open for the session; the corridor is simply gone as a wall.
const doorOpen = !!V.doorUnlocking || !!(uu && (uu[45] || 0) > 0.02)
// LOOK — pure relative mouse-look under pointer lock (the engine pauses + shows
// CLICK TO PLAY until captured, so lookX/lookY are the only look input). The old
// edge-steering (cursor-position drift) is GONE — it fought the lock, the stale
// locked cursor position kept nudging the view toward it.
V.yaw -= (inp.lookX || 0) * 0.0052 // mouse right → look right
V.pitch = Math.max(-1.2, Math.min(1.2, V.pitch - (inp.lookY || 0) * 0.0052))
// MOVE — W/S forward along yaw, A/D strafe along the right vector
const fx = Math.sin(V.yaw), fz = Math.cos(V.yaw)
const rx = -Math.cos(V.yaw), rz = Math.sin(V.yaw) // strafe right vector (A/D were flipped)
const spd = 5.5, mF = inp.moveY || 0, mS = inp.moveX || 0
const dx = (fx * mF + rx * mS) * spd * step
const dz = (fz * mF + rz * mS) * spd * step
// WARREN commit — nave-side of the column plane (z > -12.5) the wing shows BOTH
// rooms (split, warp 0); crossing the column plane latches the room to the side
// you passed, and it holds until you return nave-side of the column (the way back).
// COMMITTED rooms (A and B stay SEPARATE). Nave-side of the column (z>-12.5)
// the wing shows both split (warp 0); crossing the column LATCHES the room by
// the side you passed and HOLDS.
// THE LAIR (+2, "room 3") is a TRAP: it is STICKY — walking back past the
// column keeps you in it (you don't fall out to orange). It releases ONLY when
// you fully leave the warren back through the arch into the nave (z > -9), or
// via the behind-pillar lane below (2→1). This is the whole point of room 3.
if (V.warp === 2) {
if (V.pz > -9) V.warp = 0 // fully back in the nave → out of the trap
} else if (V.pz > WCOLZ) {
V.warp = 0
} else if (!V.warp) {
V.warp = V.px < 0 ? -1 : 1
}
// ── DRAGON WING commit (Galen: "its own instance") — the wing past the gate
// (z < -24: approach corridor → risen hall → the octagon arena) is a room of
// its OWN. Crossing the gate LATCHES the wing's reality from the room you
// crossed from (Room A → the cathedral · THE LAIR → the dragon approach +
// arena) and HOLDS it while you are inside — the warren commit is parked at
// the door and can no longer restyle or re-gate the wing (and the lair's
// ambushers stay home; see vf-lair-ambush). Crossing back releases the latch
// and the parked commit resumes, so the lair trap keeps its teeth. A drop
// straight into the arena (z < -95) latches the dragon reality — a jump is
// not a crossing. Room B (warp < 0) never latches: its deep end overlaps
// z < -24 but it is not the wing.
if (V.pz < -24 && ((V.warp || 0) >= 0.5 || V.pz < -95)) {
if (V.wingWarp == null) V.wingWarp = V.pz < -95 ? 2 : V.warp
} else if (V.wingWarp != null) { V.wingWarp = null }
const wp = V.wingWarp != null ? V.wingWarp : (V.warp || 0)
// UNSTICK — the player must never be permanently frozen inside geometry. If the
// current cell is somehow non-walkable (a wall/door state flipped under the feet,
// a warp latch left a body inside a collider), snap to the nearest walkable point
// by an outward ring search. Costs ONE walkable() call in the normal case (not
// stuck); only spirals on the rare frame it is actually needed.
if (!walkable(V.px, V.pz, wp, V.cL, doorOpen)) {
let best = null, bestD = Infinity
for (let r = 0.12; r <= 1.6 && !best; r += 0.12) {
for (let a = 0; a < 12; a++) {
const ang = a * (Math.PI / 6)
const tx = V.px + Math.cos(ang) * r, tz = V.pz + Math.sin(ang) * r
if (walkable(tx, tz, wp, V.cL, doorOpen)) { const d = r; if (d < bestD) { bestD = d; best = [tx, tz] } }
}
if (best) break // first ring with any hit wins (nearest)
}
if (best) { V.px = best[0]; V.pz = best[1] }
}
let nx = V.px, nz = V.pz
if (walkable(V.px + dx, V.pz, wp, V.cL, doorOpen)) nx = V.px + dx
if (walkable(nx, V.pz + dz, wp, V.cL, doorOpen)) nz = V.pz + dz
V.px = nx; V.pz = nz
// (The asymmetric corridor fold was removed — it hid the avenue behind solid
// rock so the open door read as a dead-end wall. The approach corridor is now a
// straight, continuous, VISIBLE passage to the risen nave. uni(46) held at the
// fixed corridor length so nothing downstream breaks.)
V.cL = CORR_LEN_IN
// ── THE LAIR flip ─────────────────────────────────────────────────────────
// Committed to Room A/lair, walking the narrow lane BEHIND a pillar (between
// the x=±2.4 pillar row and the x=±4 wall) toggles into/out of the lair (+2).
const inLane = (V.warp === 1 || V.warp === 2) &&
Math.abs(V.px) > 2.75 && Math.abs(V.px) < 3.6 && V.pz <= -12.5 && V.pz >= -24
// ONE-WAY (Galen): the lane only takes you INTO the lair (1→2). Once in room 3
// there is no lane back — the ONLY exit is the long walk out to the nave.
if (inLane && !V.laneWas && V.warp === 1) V.warp = 2
V.laneWas = inLane
// JUMP — Space (input.action edge) when grounded; gravity pulls back to the
// floor height at the player's feet (steps up onto the dais, falls off it).
const gLevel = floorH(V.px, V.pz) + 1.7
if (inp.action && V.ground) { V.vy = 4.4; V.ground = 0 }
V.vy -= 12.0 * step
V.py += V.vy * step
if (V.py <= gLevel) { V.py = gLevel; V.vy = 0; V.ground = 1 }
// CAMERA — first-person eye; look dir folds pitch in
const cp = Math.cos(V.pitch), sp = Math.sin(V.pitch)
const ta = [V.px + fx * cp * 2.0, V.py + sp * 2.0, V.pz + fz * cp * 2.0]
const u = Array.isArray(wd.gpuUniforms) ? wd.gpuUniforms : new Array(256).fill(0)
while (u.length < 256) u.push(0)
u[1] = V.px; u[2] = V.py; u[3] = V.pz; u[4] = V.yaw; u[5] = V.pitch
u[43] = V.wingWarp != null ? V.wingWarp : (V.warp || 0) // committed room (0 split · +1 A · −1 B · +2 LAIR); in the wing it is the WING's own latch, not the warren's
u[46] = V.cL // corridor current length (10 short IN / 28 long BACK) — movement-owned; rooms.wgsl reads it
u[240] = V.px; u[241] = V.py; u[242] = V.pz; u[243] = 1.2
u[244] = ta[0]; u[245] = ta[1]; u[246] = ta[2]; u[247] = 0
wd.gpuUniforms = u
}
__C['vf-player'] = movement
__C['vf-player@rev'] = __REV
}
try { __C['vf-player'](sim, dt) } catch (e) {}
})()hook · vf-warren-puzzle
by Claude (Fable · E)
;(() => {
// vf-warren-puzzle — THE WARREN — altar key + corridor lock/door puzzle, and THE RISEN NAVE growth driver (u44/u45/u48)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'de826c4d37/' + globalThis.__VF_GEO_REV
if (__C['vf-warren-puzzle@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// warren (node: warren-puzzle) — the KEY / LOCK / DOOR puzzle. Runs AFTER movement
// (reads the player pos + committed room it published) and BEFORE projectiles.
// Owns whiteboard rows 44 (keyHeld) and 45 (doorOpen). Row 46 (corridor length) is
// movement-owned (kinematic); row 47 (ambushAlert) is ambush.mjs's — untouched here.
//
// THE PUZZLE:
// · THE KEY — the glowing altar sphere on the far-end SHRINE in Room B
// (0,2.6,-27). Stand within 1.8u while committed to B (warp<0) and you pick
// it up: uni44 latches 1 and the altar SDF vanishes from B (rooms.wgsl gates
// it on uni44<0.5; the shrine pedestal remains). 1.8u clears the shrine
// collision stop (~1.5u) so the pickup always reaches.
// · THE LOCK — the barred corridor door in Room A / lair (at z=-24, x∈[-1,1]).
// With the key (uni44=1), stand within 1.6u while committed A/lair (warp>=1)
// and it unlocks: uni45 animates 0→1 over ~1.2s and the mat-4 bars withdraw.
// State lives on wd.__vf (session-scoped) and uni44/45 are DERIVED from it every
// frame, so they survive a restore. Nothing to persist beyond the session.
function warren(sim, dt) {
const wd = sim.worldData
if (!wd.__vf) wd.__vf = {}
const V = wd.__vf
const u = wd.gpuUniforms
if (!Array.isArray(u)) return
const step = Math.min(dt, 1 / 30)
const px = V.px != null ? V.px : (u[1] || 0)
const pz = V.pz != null ? V.pz : (u[3] || 0)
const warp = V.warp != null ? V.warp : (u[43] || 0)
// THE KEY — Room B far-end shrine (0,-27), pickup radius 1.8, only committed to B
if (warp < 0) {
const dx = px - 0.0, dz = pz + 27.0
if (dx * dx + dz * dz < 1.8 * 1.8) V.keyHeld = 1
}
const keyHeld = V.keyHeld ? 1 : 0
u[44] = keyHeld
// THE LOCK — corridor door at (0,-24) in Room A / lair. Key + within 1.6u latches
// the unlock; the door then blends open over ~1.2s and stays open (state-derived).
if (keyHeld && warp >= 1) {
const dx = px - 0.0, dz = pz - (-24.0)
if (dx * dx + dz * dz < 1.6 * 1.6) V.doorUnlocking = 1
}
if (V.doorUnlocking) V.doorOpen01 = Math.min(1, (V.doorOpen01 || 0) + step / 0.6) // bars retract fast (0.6s)
u[45] = V.doorOpen01 || 0
// ── THE RISEN NAVE growth driver — uni(48) = cathedral GROWTH 0..1 ──────────
// Past the corridor door the avenue OPENS into a grown Gothic street
// (veilfire/cathedral.wgsl / mod_cath). Its LIVE GROWTH reads uni(48): 0 at the
// avenue mouth (RISEN_Z0), 1 at the far gable (RISEN_Z1). We drive it from the
// player's progress down the avenue so nearer bays are grown and far ones rise
// as you approach (cellStagger 0.6 in the emitted WGSL handles per-bay).
// Coords MIRROR cathedral.wgsl's PLOT/LINE CONTRACT exactly (source of truth):
// near mouth z = -56 → gable plane z = -96 (len 40u, running -z).
// Rules (Galen, Jul 27→28): the player must WATCH it rise. Growth is anchored to
// how far the player has PENETRATED the avenue, and stays ~0 until they reach the
// mouth — so nothing finishes building while they are still blind in the corridor.
// It then rises gradually over the whole walk in (GROW_SPAN), a small LOOKAHEAD so
// the frontier just ahead is the first to stir, and it UN-GROWS as they walk back
// out (no ratchet — alive, built by presence). EASED both ways, slow enough that
// the rise can never outrun the player's own approach.
const RISEN_Z0 = -56.0 // avenue mouth (growth begins HERE, where it comes into view)
const RISEN_Z1 = -96.0 // far gable plane
const GROW_SPAN = 28.0 // distance walked in over which it rises 0→1 (target full ~z=-79, eased to full before the gable)
const RISE_RATE = 1.7 // gentle ease (per second, both directions) — tracks the walk without snapping
const MAX_DELTA = 0.02 // per-frame cap — the rise cannot snap; it tracks the walk
const LOOKAHEAD = 5.0 // frontier stirs THIS far ahead — just enough to see it start, not the whole street early
let cur = V.growth01
if (!Number.isFinite(cur)) cur = 0
const doorOpen = Number.isFinite(u[45]) && u[45] >= 0.5
let target = cur // HOLD if the door is somehow shut
if (doorOpen && GROW_SPAN > 0) {
// progress = how far past the mouth the player (plus a small lookahead) has gone,
// 0 at/above the mouth → 1 deep in the avenue. Walking back out lowers it.
const zAhead = (Number.isFinite(pz) ? pz : 0) - LOOKAHEAD
let prog = (RISEN_Z0 - zAhead) / GROW_SPAN
if (!Number.isFinite(prog)) prog = 0
target = Math.min(1, Math.max(0, prog))
}
if (!Number.isFinite(target)) target = cur
// exponential ease toward target, then a hard delta clamp (belt + braces)
const easeK = Math.min(1, Math.max(0, step * RISE_RATE))
let next = cur + (target - cur) * easeK
const delta = next - cur
if (delta > MAX_DELTA) next = cur + MAX_DELTA
else if (delta < -MAX_DELTA) next = cur - MAX_DELTA
if (!Number.isFinite(next)) next = 0
next = Math.min(1, Math.max(0, next))
V.growth01 = next
u[48] = next
}
__C['vf-warren-puzzle'] = warren
__C['vf-warren-puzzle@rev'] = __REV
}
try { __C['vf-warren-puzzle'](sim, dt) } catch (e) {}
})()hook · vf-deathfx
by Claude (Fable · E)
;(() => {
// vf-deathfx — DEATH FX — ember burst bits per death event; clears V.deaths
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = '517ab0cb40/' + globalThis.__VF_GEO_REV
if (__C['vf-deathfx@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// death-fx (node: death-fx) — ember death bursts (the pixelburst idea in 3D).
// Fragment run after combat. On each death event, emit ember bits (kind 5) that
// arc up, fall under gravity, and cool out; pushed to __vf.pop. Renderer colors
// kind 5 as ember by heat.
function deathfx(sim, dt) {
const wd = sim.worldData
const V = wd.__vf
const step = Math.min(dt, 1 / 30)
if (!V.bits) V.bits = []
if (V.deaths) {
for (const d of V.deaths) {
for (let i = 0; i < 22; i++) {
const a = i * 0.618 * 6.283, up = 1.6 + (i % 5) * 0.5, out = 1.0 + (i % 7) * 0.28
V.bits.push({ x: d.x, y: d.y, z: d.z, dx: Math.cos(a) * out, dy: up, dz: Math.sin(a) * out, age: 0, life: 0.6 + (i % 4) * 0.12 })
}
}
V.deaths = []
}
const alive = []
for (const p of V.bits) {
p.age += step
if (p.age >= p.life) continue
p.dy -= 7.0 * step
p.x += p.dx * step; p.y += p.dy * step; p.z += p.dz * step
if (p.y < 0.05) { p.y = 0.05; p.dy = 0 }
alive.push(p)
V.pop.push(p.x, p.y, p.z, 5.0, Math.max(0, 1 - p.age / p.life), 0, 0, 0)
}
V.bits = alive
}
__C['vf-deathfx'] = deathfx
__C['vf-deathfx@rev'] = __REV
}
try { __C['vf-deathfx'](sim, dt) } catch (e) {}
})()hook · vf-flush
by Claude (Fable · E)
;(() => {
// vf-flush — FLUSH: publish the population buffer + clock uniform. Runs LAST of the carved core.
const wd = sim.worldData
if (!wd.__vf) return
wd.gpuPopulation = wd.__vf.pop
if (Array.isArray(wd.gpuUniforms)) wd.gpuUniforms[0] = wd.__vf.t
})()hook · vf-timecells
by Claude (Fable · E)
// VEILFIRE-3D · TIME COLUMNS — two fixed zones in the SIDE CHAMBER (x∈[5,11]).
// BLUE column SLOWS the player, GOLD column SPEEDS them up. Renders glowing
// columns and scales the base movement by the zone the player stands in.
// MUST run AFTER the base `veilfire` movement hook: it re-scales the displacement
// the base APPLIED this frame, so if it ran first the base would overwrite it
// (that was the "time puzzle broken" bug — re-pushing this hook moves it last).
try {
const wd = sim.worldData, V = wd.__vf
if (V && Array.isArray(V.pop)) {
if (V.weapon == null) V.weapon = 2 // veilfire starts on the GUN
const t = V.t || 0
const px = V.px || 0, pz = V.pz || 0
// the two TIME COLUMNS — fixed in the side room, never wander
const BLUE = { x: 6.5, z: -2.0, r: 1.5 } // slow field
const GOLD = { x: 9.5, z: 2.0, r: 1.5 } // fast field
// low FLOOR markers only — a small glowing disc at each zone (nothing rises
// toward the ceiling). blue = kind 3.3 plasma-blue · gold = kind 7.
for (let a = 0; a < 6; a++) {
const ang = a * (Math.PI / 3), rr = 0.5
V.pop.push(BLUE.x + Math.cos(ang) * rr, 0.12, BLUE.z + Math.sin(ang) * rr, 3.3, 0.7, 0, 0, 0)
V.pop.push(GOLD.x + Math.cos(ang) * rr, 0.12, GOLD.z + Math.sin(ang) * rr, 7, 0.7, 0, 0, 0)
}
// ── movement scaling: slow in BLUE, fast in GOLD — by re-scaling the base's
// applied displacement this frame (works with FPS + collision). ──
if (V.__tcPX == null) { V.__tcPX = px; V.__tcPZ = pz }
let dxm = px - V.__tcPX, dzm = pz - V.__tcPZ
// teleport/reset guard: a respawn or warp jump is not "movement" to rescale
if (Math.hypot(dxm, dzm) > 3) { V.__tcPX = px; V.__tcPZ = pz; dxm = 0; dzm = 0 }
const inBlue = Math.hypot(px - BLUE.x, pz - BLUE.z) < BLUE.r
const inGold = Math.hypot(px - GOLD.x, pz - GOLD.z) < GOLD.r
if (inBlue) { V.px = V.__tcPX + dxm * 0.32; V.pz = V.__tcPZ + dzm * 0.32 } // SLOW
else if (inGold) { V.px = V.__tcPX + dxm * 1.9; V.pz = V.__tcPZ + dzm * 1.9 } // FAST
V.__tcPX = V.px; V.__tcPZ = V.pz
}
} catch (e) { /* never disturb the base game */ }
hook · vf-heal
by Claude (Fable · E)
try{const V=sim.worldData.__vf; if(V&&!V.__healed2){V.hp=1;V.weapon=2;V.__healed2=1}}catch(e){}hook · vf-backroom
by Claude (Fable · E)
try {
const wd=sim.worldData, V=wd.__vf
if (V && Array.isArray(V.pop)){
const rnd=()=>{ try{ const r=sim.rand&&sim.rand(); if(Number.isFinite(r))return r }catch(e){} return 0.5 }
const px=V.px||0, pz=V.pz||0, sdt=Math.min(dt||0.016,1/30)
const inRoom = px>=-4 && px<=4 && pz>=9.3 && pz<=17.3
const BLUE=3.3, PINK=5.9, WHITE=7
if (!V.__bp){ V.__bp=[]; for(let i=0;i<16;i++){ const a=i/16*6.2832
V.__bp.push({x:Math.cos(a)*2.2, y:1.4+(i%4)*0.7, z:13.5+Math.sin(a)*2.2, vx:0,vy:2+rnd()*2,vz:0}) } } // start bouncing
if (inRoom){
if(!V.__bpSeen){ V.__bpSeen=1
wd.__play_sound=[{frequency:523,duration:0.1,volume:0.2,type:'sine'},{frequency:784,duration:0.12,volume:0.16,type:'sine'},{frequency:1046,duration:0.2,volume:0.14,type:'triangle'}] }
// RE-ENTRY JUMP-PAD (back-right corner) — stand on the gold ring and JUMP to
// warp back into the void chamber. Off in the corner so the puzzle can't hit it.
{ const jx=3.0, jz=16.0
V.pop.push(jx, 0.12, jz, 7, 0.8, 0,0,0)
for(let r=0;r<7;r++){ const a=r/7*6.2832; V.pop.push(jx+Math.cos(a)*0.6, 0.12, jz+Math.sin(a)*0.6, 7, 0.55,0,0,0) }
V.pop.push(jx, 1.2+0.15*Math.sin((V.t||0)*3), jz, 7, 0.9, 0,0,0) // a hovering beacon
if(Math.hypot(px-jx, pz-jz) < 0.85 && (V.vy||0) > 2.6){ // jumped while on the pad
V.px=0; V.pz=40; V.py=1.7; V.vy=0; V.__tcPX=0; V.__tcPZ=40
wd.__play_sound=[{frequency:1200,duration:0.3,volume:0.22,type:'sine'},{frequency:1800,duration:0.4,volume:0.14,type:'sine'}] } }
const ptr=(wd.input||{}).pointer||{}
const firing=(V.weapon===1)&&!!(ptr&&ptr.pressed)
const edge=firing&&!V.__bpFire; V.__bpFire=firing
if(edge && (V.__bpFreeze||0)<=0 && (V.__bpWin||0)<=0){ V.__bpFreeze=1.3
wd.__play_sound=[{frequency:1500,duration:0.12,volume:0.18,type:'sine'},{frequency:950,duration:0.22,volume:0.12,type:'triangle'}] }
const frozen=(V.__bpFreeze||0)>0
if(frozen){ V.__bpFreeze-=sdt
if(V.__bpFreeze<=0){ for(const b of V.__bp){ b.vy=6.4+rnd()*0.5; b.vx*=0.3; b.vz*=0.3 }
wd.__play_sound=[{frequency:680,duration:0.1,volume:0.2,type:'square'}] } }
let allBlue=true
for(const b of V.__bp){
if(!frozen && (V.__bpWin||0)<=0){
b.vy-=5.6*sdt; b.x+=b.vx*sdt; b.y+=b.vy*sdt; b.z+=b.vz*sdt
if(b.y<0.35){ b.y=0.35; b.vy=Math.abs(b.vy)*0.86+2.3; b.vx+=(rnd()-0.5)*0.7; b.vz+=(rnd()-0.5)*0.7 } // livelier bounce
if(b.x<-3.6){b.x=-3.6;b.vx=Math.abs(b.vx)} if(b.x>3.6){b.x=3.6;b.vx=-Math.abs(b.vx)}
if(b.z<9.8){b.z=9.8;b.vz=Math.abs(b.vz)} if(b.z>17.2){b.z=17.2;b.vz=-Math.abs(b.vz)}
const d=Math.hypot(b.x-px,b.z-pz)
if(d<1.15){ const f=(1.15-d)*22; b.vx+=(b.x-px)/(d+0.2)*f*sdt; b.vz+=(b.z-pz)/(d+0.2)*f*sdt; b.vy+=2.5 }
b.vx*=0.996; b.vz*=0.996
}
const k = frozen ? WHITE : (b.vy>0.4 ? BLUE : (b.vy<-0.4 ? PINK : WHITE))
if(b.vy<=0.4) allBlue=false
V.pop.push(b.x, b.y, b.z, k, 1,0,0,0)
}
if(allBlue && !frozen && (V.__bpWin||0)<=0){ V.__bpWin=1.7
wd.__play_sound=[{frequency:220,duration:0.5,volume:0.28,type:'sawtooth'},{frequency:1760,duration:0.5,volume:0.18,type:'sine'}] }
if((V.__bpWin||0)>0){ V.__bpWin-=sdt
for(let i=0;i<26;i++){ const a=i/26*6.2832
V.pop.push(Math.cos(a)*3.5, 0.35+(i%6)*0.85, 13.5+Math.sin(a)*3.5, BLUE, 1,0,0,0) }
V.shake=Math.max(V.shake||0, 0.35+(1.7-V.__bpWin)*0.5) // ramps as the floor opens
if(V.__bpWin<=0){ // PORTAL — fall through into the VOID CHAMBER (z=40)
V.px=0; V.pz=40; V.py=1.7; V.vy=0; V.__tcPX=0; V.__tcPZ=40
wd.__play_sound=[{frequency:1300,duration:0.35,volume:0.22,type:'sine'},{frequency:120,duration:0.6,volume:0.3,type:'sine'},{frequency:60,duration:0.8,volume:0.2,type:'triangle'}]
V.shake=1.0
for(let i=0;i<V.__bp.length;i++){ const b=V.__bp[i], a=i/16*6.2832
b.x=Math.cos(a)*2.2; b.y=1.4+(i%4)*0.7; b.z=13.5+Math.sin(a)*2.2; b.vx=0;b.vy=2;b.vz=0 } }
}
} else { V.__bpSeen=0; V.__bpFreeze=0; V.__bpWin=0 }
}
} catch(e){}
hook · vf-lawform
by Claude (Fable · E)
try {
const wd=sim.worldData, V=wd.__vf
if (V){
const px=V.px||0, pz=V.pz||0, t=V.t||0
const LAWS=[ { x:6.5, z:-2.0, r:1.7, factor:0.35, col:3.3 }, // BLUE slow
{ x:9.5, z:2.0, r:1.7, factor:1.9, col:7 } ]; // GOLD fast
const lawAt=(x,z)=>{ for(const L of LAWS){ const dx=x-L.x, dz=z-L.z; if(dx*dx+dz*dz < L.r*L.r) return L } return null }
if (Array.isArray(V.en)) for(const e of V.en){
if(!e || (e.hp!=null && e.hp<=0)) continue
if(e.__lpx==null){ e.__lpx=e.x; e.__lpz=e.z }
const L=lawAt(e.x, e.z)
if(L){ const dx=e.x-e.__lpx, dz=e.z-e.__lpz
if(dx*dx+dz*dz < 9){ e.x=e.__lpx+dx*L.factor; e.z=e.__lpz+dz*L.factor }
if(Array.isArray(V.pop)) V.pop.push(e.x, (e.y!=null?e.y:0.9)+0.2, e.z, L.col, 0.7, 0,0,0) }
e.__lpx=e.x; e.__lpz=e.z
}
const PL=lawAt(px,pz)
if(PL && Array.isArray(V.pop)) for(let i=0;i<5;i++){ const a=t*2.2 + i/5*6.2832
V.pop.push(px+Math.cos(a)*0.6, 1.95+0.18*Math.sin(t*3+i), pz+Math.sin(a)*0.6, PL.col, 0.5, 0,0,0) }
}
} catch(e){}
hook · vf-voidroom
by Claude (Fable · E)
try {
const wd=sim.worldData, V=wd.__vf
if(V && Array.isArray(V.pop)){
const px=V.px||0, pz=V.pz||0, t=V.t||0, sdt=Math.min(dt||0.016,1/30)
const inVoid = px>=-4 && px<=4 && pz>=36 && pz<=44
if(inVoid){
if(!V.__voidSeen){ V.__voidSeen=1
wd.__play_sound=[{frequency:392,duration:0.3,volume:0.2,type:'sine'},{frequency:588,duration:0.4,volume:0.14,type:'sine'},{frequency:784,duration:0.5,volume:0.1,type:'triangle'}] }
for(let i=0;i<18;i++){ const a=i*2.399+t*0.18
V.pop.push(Math.cos(a)*3.4, 0.5+(i%6)*0.95, 40+Math.sin(a*1.3)*3.4, [3.3,5.9,7][i%3], 0.4, 0,0,0) }
const cy=2.5+0.15*Math.sin(t*1.4)
V.pop.push(0, cy, 40, 7, 1,0,0,0)
V.pop.push(0.35*Math.cos(t*2.2), cy+0.1, 40+0.35*Math.sin(t*2.2), 5.9, 1,0,0,0)
// RETURN PAD (back wall) — JUMP and LAND on it to go home. Fires the instant you
// touch down on the pad from a jump: NOT on the way up, no dwell (Galen: land-of-jump).
const rz=43.2
const onPad = Math.hypot(px-0, pz-rz) < 1.0
const grounded = (V.ground!==0) && (V.vy||0) <= 0.2
const landed = grounded && !!V.__rWasAir && onPad
V.__rWasAir = grounded ? 0 : 1
for(let r=0;r<3;r++){ const rr=0.5+r*0.28
V.pop.push(Math.cos(t*1.5+r)*rr, 0.15, rz+Math.sin(t*1.5+r)*rr, onPad?7:3.3, 0.6, 0,0,0) }
V.pop.push(0, 1.4+0.12*Math.sin(t*2.4), rz, onPad?7:3.3, 1,0,0,0)
if(landed){
V.px=0; V.pz=-6; V.py=1.7; V.vy=0; V.__tcPX=0; V.__tcPZ=-6; V.__voidSeen=0; V.__rWasAir=0
wd.__play_sound=[{frequency:660,duration:0.15,volume:0.2,type:'sine'},{frequency:440,duration:0.28,volume:0.14,type:'triangle'}] }
} else { V.__voidSeen=0; V.__rWasAir=0 }
}
} catch(e){}
hook · vf-dragonflash
by Claude (Fable · E)
// VEILFIRE-3D · DRAGON HURT FLASH — RETIRED (Aug 5): the flash now lives ON the
// dragon's body (s3 dragon branch blanches white via uni(57) = flinch01 from
// vf-arena-dragon). The old overlay spawned 30 white tracer points around/above
// the head — depth-occluded when inside the body, floating debris when outside
// ("stuff floating above its head"). Kept as a no-op node so the registry slot
// and history survive; delete freely if it's still empty in a month.
hook · vf-entities
by Claude (Fable · E)
// veilfire retrofit — publish worldData.__entities so the ENGINE's inspect toggle
// names veilfire's creatures: the DEMONS (__vf.en), the DRAGON boss (__vf.dragon),
// and the ORB blob-creature (__vf.orb) — all invisible to the field hit-map (the
// whole world is one raymarch field). Projects each through the SAME camera the
// shader marches (uniforms u[240..246], fov u[243]) so a click resolves
// "Scene › entity #N (dragon)". Additive: reads state, writes only wd.__entities.
try {
const wd = sim.worldData
const V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u) && u.length >= 247) {
const ro = [u[240], u[241], u[242]], fov = u[243] || 1.2, ta = [u[244], u[245], u[246]]
const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
const norm = (v) => { const l = Math.hypot(v[0], v[1], v[2]) || 1; return [v[0] / l, v[1] / l, v[2] / l] }
const fw = norm(sub(ta, ro))
const rgt = norm(cross([0, 1, 0], fw)) // worldup × fwd
const up = cross(fw, rgt)
const ents = []
// project a world point → a screen entity, or null if behind/off-screen
const projEnt = (W, id, kind, label, size) => {
const d = sub(W, ro), df = dot(d, fw)
if (df <= 0.3) return null
const xc = dot(d, rgt) / df, yc = dot(d, up) / df
const sx = (1 - xc / fov) * 256 // veilfire ray uses rt = cross(fw, worldup) → mirror X (calibrated live)
const sy = (1 - yc / fov) * 256
if (sx < -40 || sx > 552 || sy < -40 || sy > 552) return null
const r = Math.max(28, Math.min(140, (size / (fov * Math.max(df, 0.5))) * 256))
return { id, kind, label, sx, sy, r }
}
// DEMONS (kind 1)
if (Array.isArray(V.en)) for (let i = 0; i < V.en.length; i++) {
const e = V.en[i]
if (!e || e.hp <= 0) continue
const en = projEnt([e.x, 0.9, e.z], i, 1, 'demon', 1.1)
if (en) ents.push(en)
}
// DRAGON (kind 10) — the boss at the end of the labyrinth
const D = V.dragon
if (D && (D.present == null || D.present === 1) && (D.hp == null || D.hp > 0)) {
const en = projEnt([D.x, (D.y || 0) + 1.4, D.z], 900, 10, 'Pentarch', 3.0)
if (en) ents.push(en)
}
// ORB (kind 11) — the blob creature; center = blob centroid
const O = V.orb
if (O && Array.isArray(O.blobs) && O.blobs.length && !V.orbDead) {
let cx = 0, cy = 0, cz = 0
for (const b of O.blobs) { cx += b.x; cy += b.y; cz += b.z }
const n = O.blobs.length
const en = projEnt([cx / n, cy / n, cz / n], 901, 11, 'orb', 1.6)
if (en) ents.push(en)
}
// THE ALTAR KEY (kind 2) — shader-drawn from uniforms (never in the population
// buffer), so it was invisible to inspect until published here. Shrine sphere
// (0, 2.6, -27), Room B only (warp < -0.5) or the split view (|warp| < 0.5),
// gone once held (u[44]).
const keyHeld = (u[44] || 0) > 0.5, warp = u[43] || 0
if (!keyHeld && warp < 0.5) {
const en = projEnt([0, 2.6, -27], 902, 2, 'altar key', 0.7)
if (en) ents.push(en)
}
// ROOMS (kind 3) — the world is ONE raymarch field, so a click on a wall
// resolves to "Scene" and nothing else; publish each ROOM at its center so
// inspect names the place (and its owning node). Warp-aware: the warren
// superposes Room A / Room B / THE LAIR in the same volume (u43: 0 split,
// +1 A, -1 B, +2 lair). projEnt culls off-screen/behind automatically.
const rooms = []
rooms.push([[0, 1.5, 0], 'THE NAVE'])
rooms.push([[8, 1.5, 0], 'SIDE CHAMBER'])
rooms.push([[0, 1.5, 13], 'SECRET ROOM'])
rooms.push([[14, 1.5, 0], 'ESCAPE HALLWAY'])
rooms.push([[0, 1.5, 40], 'VOID CHAMBER'])
if (warp > 1.5) rooms.push([[0, 1.5, -18], 'THE LAIR · vf-lair-ambush'])
else if (warp < -0.5) rooms.push([[0, 1.5, -21], 'WARREN · ROOM B · vf-warren-puzzle'])
else rooms.push([[0, 1.5, -18], 'WARREN · ROOM A · vf-warren-puzzle'])
rooms.push([[0, 1.5, -39], 'APPROACH CORRIDOR'])
rooms.push([[0, 2.0, -75], 'THE RISEN NAVE · vf-warren-puzzle'])
rooms.push([[4.012, 1.5, -106], 'OCTAGON ARENA · vf-arena-dragon'])
for (let ri = 0; ri < rooms.length; ri++) {
const en = projEnt(rooms[ri][0], 910 + ri, 3, rooms[ri][1], 2.4)
if (en) ents.push(en)
}
// THE UPGRADED CRYSTAL (kind 2) — the dragon's drop, until taken
if (V.crystal && !V.crystal.taken) {
const en = projEnt([V.crystal.x, 1.1, V.crystal.z], 903, 2, 'upgraded crystal', 0.7)
if (en) ents.push(en)
}
wd.__entities = ents
// strip any stale debug markers left in wd.hud (from the calibration pass)
if (Array.isArray(wd.hud)) { const f = wd.hud.filter(x => !(x && x.id && (String(x.id).startsWith('vfm') || x.id === 'vftitle'))); wd.hud = f.length ? f : undefined }
}
} catch (e) { /* never disturb the game */ }
hook · vf-arena-blade
by Claude (Fable · E)
;(() => {
// vf-arena-blade — THE OCTAGON ARENA — the scatterblade: floating sword pickup, throw/spin/cut, weapon slot 4
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'dabbb7d9d3/' + globalThis.__VF_GEO_REV
if (__C['vf-arena-blade@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// scatterblade (Galen: the THIRD WEAPON) — a floating sword picked up mid-arena.
// Always visible: hovers at your right shoulder. CLICK sends it flying a set
// distance out along your look; there it SPINS and attacks indefinitely —
// demons, ambushers, the dragon — until you click it somewhere else.
function scatterblade(sim, dt) {
try {
const wd = sim.worldData, V = wd.__vf
if (!V || !Array.isArray(V.pop)) return
const B = V.blade || (V.blade = { have: 0, x: 0, y: 1.05, z: 13, mode: 'seed', spin: 0 })
B.spin = (B.spin + dt * (B.mode === 'out' ? 2.6 : 0.9)) % 1
const px = V.px || 0, pz = V.pz || 0, py = V.py != null ? V.py : 1.7
const yaw = V.yaw || 0, pitch = V.pitch || 0
if (!B.have) {
// the pickup: the blade itself, hovering mid-arena on a slow bob
B.x = 0; B.z = 13; B.y = 1.05 + Math.sin((V.t || 0) * 1.6) * 0.14 // SECRET ROOM seed — no pickups in hallways or the dragon room (Galen)
V.pop.push(B.x, B.y, B.z, 11, B.spin, 0, 0, 0)
if (Math.hypot(px - B.x, pz - B.z) < 1.6) {
B.have = 1; B.mode = 'hover'
V.hasW4 = 1; V.weapon = 4 // dragon-room pickup ARMS weapon slot 4 (the blade)
wd.__play_sound = [{ frequency: 1240, duration: 0.16, volume: 0.16, type: 'sine' }, { frequency: 1860, duration: 0.22, volume: 0.1, type: 'sine' }]
}
return
}
// WEAPON 4 ONLY: the blade throws / hovers / renders only while slot 4 is the
// active weapon; any other weapon holsters it (no throw, no viewmodel). Keep __pl
// synced so switching back mid-click can't fire a phantom throw.
if (V.weapon !== 4) { B.__pl = !!(wd.input && wd.input.pointer && wd.input.pointer.pressed); if (B.mode === 'out') { B.mode = 'hover' } return }
// CLICK → fly out to a fixed 7u along the look direction (re-click re-sends)
const inp = wd.input || {}
const pressed = !!(inp.pointer && inp.pointer.pressed)
if (pressed && !B.__pl) {
const cp = Math.cos(pitch)
B.mode = 'out'
B.ox = px + Math.sin(yaw) * cp * 7
B.oz = pz + Math.cos(yaw) * cp * 7
B.oy = Math.max(0.8, Math.min(3.2, py + Math.sin(pitch) * 7))
wd.__play_sound = [{ frequency: 980, duration: 0.09, volume: 0.11, type: 'square' }]
}
B.__pl = pressed
if (B.mode === 'out') {
// glide toward its post, then hold there spinning
B.x += ((B.ox) - B.x) * Math.min(1, dt * 9)
B.y += ((B.oy) - B.y) * Math.min(1, dt * 9)
B.z += ((B.oz) - B.z) * Math.min(1, dt * 9)
// ATTACK: anything living within reach takes continuous cuts
const R9 = 1.5, DPS = 1.6
if (Array.isArray(V.en)) for (const e9 of V.en) {
if (e9.hp > 0 && Math.hypot(e9.x - B.x, e9.z - B.z) < R9) { e9.hp -= DPS * dt; V.bladeHit = 0.3 }
}
if (Array.isArray(V.amb)) for (const a9 of V.amb) {
if (!a9.dead && a9.hp > 0 && Math.hypot(a9.x - B.x, a9.z - B.z) < R9) { a9.hp -= DPS * dt; V.bladeHit = 0.3 }
}
const D9 = V.dragon
if (D9 && !D9.dead && Math.hypot(D9.x - B.x, D9.z - B.z) < R9 + 3.0) {
D9.hp -= DPS * 0.5 * dt; V.bladeHit = 0.3
if (D9.hp <= 0) { D9.dead = true; for (let k9 = 0; k9 < 6; k9++) D9.bursts.push({ x: D9.x, y: 1 + k9 * 0.8, z: D9.z, age: -k9 * 0.12, life: 0.6 }) }
}
} else {
// HOVER — always in sight at the right shoulder, breathing
const rx = Math.cos(yaw), rz = -Math.sin(yaw)
B.x = px + Math.sin(yaw) * 0.55 + rx * 0.5
B.z = pz + Math.cos(yaw) * 0.55 + rz * 0.5
B.y = py - 0.25 + Math.sin((V.t || 0) * 2.2) * 0.07
}
V.bladeHit = Math.max(0, (V.bladeHit || 0) - dt)
V.pop.push(B.x, B.y, B.z, 11, B.spin, (V.bladeHit || 0) > 0 ? 1 : 0, 0, 0)
} catch (e) { /* never throw — composed with siblings */ }
}
__C['vf-arena-blade'] = scatterblade
__C['vf-arena-blade@rev'] = __REV
}
try { __C['vf-arena-blade'](sim, dt) } catch (e) {}
})()hook · vf-combat
by Claude (Fable · E)
;(() => {
// vf-combat — COMBAT — bolt/demon hit-tests, strikes onto player HP, hit-flash, shake, score, game-over (u6,8,14,16,17)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'a4e4864730-fx1/' + globalThis.__VF_GEO_REV
if (__C['vf-combat@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// combat (node: combat) — hit detection + health + score. Fragment run after
// projectiles. Player bolts vs demons (__vf.en) → damage/kill + score + death
// event; demon strikes (__vf.hits from enemies) → player HP down + hit-flash +
// shake; gameState → dead at 0. Pure sim; owns rows 6 (HP), 8 (score),
// 16 (hitFlash), 17 (shake), 14 (gameState).
function combat(sim, dt) {
const wd = sim.worldData
const V = wd.__vf
const step = Math.min(dt, 1 / 30)
if (V.score == null) V.score = 0
if (V.hp == null) V.hp = 1.0
if (!V.deaths) V.deaths = []
// player bolts vs demons
if (V.bolts && V.en) {
for (const b of V.bolts) {
if (b.life <= 0) continue
for (const e of V.en) {
if (e.hp <= 0) continue
const dx = b.x - e.x, dy = b.y - 1.0, dz = b.z - e.z
if (dx * dx + dy * dy + dz * dz < 0.85 * 0.85) {
e.hp -= 0.5; e.hurt = 1; b.life = 0; V.hitFlash = 0.55 // hurt → the FORM freaks out (shader decodes slot4 overload)
if (e.hp <= 0) { V.score += 100; V.deaths.push({ x: e.x, y: 1.0, z: e.z }) }
break
}
}
}
}
// ambusher kills (ambush.mjs owns their self-contained hit-test + death embers;
// it queues each kill here so score stays row 8's single source of truth)
if (V.ambKills) { V.score += 100 * V.ambKills; V.ambKills = 0 }
// demon strikes vs player
if (V.hits) for (const h of V.hits) { V.hp = Math.max(0, V.hp - h.dmg); V.hitFlash = 0.9; V.shake = 0.5 }
V.hitFlash = Math.max(0, (V.hitFlash || 0) - step * 3)
V.shake = Math.max(0, (V.shake || 0) - step * 2)
V.game = V.hp <= 0 ? 1 : 0
const u = wd.gpuUniforms
if (u) { u[6] = V.hp; u[8] = V.score; u[16] = V.hitFlash; u[17] = V.shake; u[14] = V.game }
}
__C['vf-combat'] = combat
__C['vf-combat@rev'] = __REV
}
try { __C['vf-combat'](sim, dt) } catch (e) {}
})()hook · vf-nave-demons
by Claude (Fable · E)
;(() => {
// vf-nave-demons — THE NAVE — 4 demons: circle/lunge/retreat AI, separation, wall-slide (V.en, resets V.hits)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = '28b4f6afd7-fx1/' + globalThis.__VF_GEO_REV
if (__C['vf-nave-demons@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// enemies (node: enemies) — the baddie AI. A step-hook FRAGMENT run after
// movement. Demons CIRCLE-STRAFE the player at a standoff ring (dodging, lateral
// motion, facing their movement so you see their tracking eyes), then dart in to
// LUNGE (square up, wind up, strike), then RETREAT back to the ring. They hold a
// standoff (never clip the camera), SEPARATE (never stack), turn SMOOTHLY (no
// snap), and COLLIDE with the walls (mirrors movement's walkable mask).
//
// POPULATION CONTRACT (SPEC): each enemy = TWO entries pushed to the shared
// sim.worldData.__vf.pop (vf-integrate clears it each frame and flushes to
// wd.gpuPopulation): (x, y=0, z, kind=1) , (hp01, gaitPhase, yaw, atk 0..1).
// Reads the player at __vf.px/__vf.pz (movement owns those). Sets __vf.hits for
// combat: an entry per striking enemy so combat can damage the player.
// standoff geometry (world units, horizontal):
// MIN 1.9 — hard floor: camera stays OUTSIDE the demon's render sphere (no clip)
// RING 2.7 — orbit distance while circling
// LUNGE_IN 2.15 — during a lunge, strike when this close
const DM_MIN = 1.9, DM_RING = 2.7, DM_LUNGE_IN = 2.15, DM_SEP = 1.7, DM_R = 0.35
// wall collision — mirrors movement.mjs walkable()/blocked() with a demon radius
function dmBlocked(x, z) {
const cr = 0.4 + DM_R
for (const cx of [-3.2, 3.2]) for (const cz of [-8, -4, 0, 4, 8]) {
if ((x - cx) * (x - cx) + (z - cz) * (z - cz) < cr * cr) return true
}
return false
}
function dmWalk(x, z) {
const nave = x >= -4 + DM_R && x <= 4 - DM_R && z >= -9 + DM_R && z <= 9 - DM_R
const side = x >= 5 + DM_R && x <= 11 - DM_R && z >= -3.5 + DM_R && z <= 3.5 - DM_R
const door = x >= 3.3 && x <= 5.7 && z >= -1.5 + DM_R && z <= 1.5 - DM_R
return (nave || side || door) && !dmBlocked(x, z)
}
// shortest-arc angle step for smooth (non-snapping) turning
function angLerp(a, b, t) {
let d = (b - a) % (2 * Math.PI)
if (d > Math.PI) d -= 2 * Math.PI
if (d < -Math.PI) d += 2 * Math.PI
return a + d * t
}
function enemies(sim, dt) {
const wd = sim.worldData
if (!wd.__vf) wd.__vf = {}
const V = wd.__vf
const step = Math.min(dt, 1 / 30)
const rnd = () => (sim.rand ? sim.rand() : Math.random())
if (!V.en) {
V.en = [ // spawn in nave + side chamber
{ x: 1.5, z: 5, ph: 0, hp: 1, atk: 0, yaw: 0, side: 1, juke: 1.0, ringOff: -0.4, st: 'circle', cd: 1.5 + rnd() * 2 },
{ x: -2.0, z: 8, ph: 1.5, hp: 1, atk: 0, yaw: 0, side: -1, juke: 1.6, ringOff: 0.2, st: 'circle', cd: 1.5 + rnd() * 2 },
{ x: 2.5, z: -1, ph: 3, hp: 1, atk: 0, yaw: 0, side: 1, juke: 0.7, ringOff: 0.7, st: 'circle', cd: 1.5 + rnd() * 2 },
{ x: 7.5, z: 0, ph: 0.7, hp: 1, atk: 0, yaw: 0, side: -1, juke: 1.3, ringOff: 1.2, st: 'circle', cd: 1.5 + rnd() * 2 },
]
}
const px = V.px ?? 0, pz = V.pz ?? -6
if (!V.pop) V.pop = []
V.hits = []
const SPD = 1.9, LUNGE_SPD = 4.2
for (const e of V.en) {
if (e.hp <= 0) continue
const dx = px - e.x, dz = pz - e.z
const dist = Math.hypot(dx, dz) || 1e-4
const ux = dx / dist, uz = dz / dist // unit toward player
const tx = -uz, tz = ux // perpendicular (left)
const ring = DM_RING + (e.ringOff || 0)
// separation from the other demons (always on) → never stack
let sepx = 0, sepz = 0
for (const o of V.en) {
if (o === e || o.hp <= 0) continue
const ox = e.x - o.x, oz = e.z - o.z
const od = Math.hypot(ox, oz)
if (od > 1e-3 && od < DM_SEP) { const w = DM_SEP / Math.max(od, 0.35) - 1.0; sepx += (ox / od) * w; sepz += (oz / od) * w }
}
// ── STATE MACHINE: circle → lunge → retreat ──────────────────────────────
let vx = 0, vz = 0, faceMove = true, spd = SPD
if (e.st === 'circle') {
let radial = 0
if (dist > ring + 0.15) radial = 1
else if (dist < Math.max(DM_MIN + 0.2, ring - 0.3)) radial = -1.0
e.juke -= step
if (e.juke <= 0) { e.side = rnd() < 0.5 ? -1 : 1; e.juke = 0.7 + rnd() * 1.3 }
const strafe = e.side * 0.85
vx = ux * radial + tx * strafe
vz = uz * radial + tz * strafe
e.atk = Math.max(0, e.atk - step * 2.0)
e.cd -= step
if (e.cd <= 0 && dist < ring + 1.8) { e.st = 'lunge'; e.atk = 0 } // commit to a strike
} else if (e.st === 'lunge') {
spd = LUNGE_SPD
vx = ux; vz = uz // drive straight in
faceMove = false // square up to the player
e.atk = Math.min(1, e.atk + step * 1.4) // wind up → strike
if (dist <= DM_LUNGE_IN && e.atk >= 1) { V.hits.push({ dmg: 0.12, x: e.x, z: e.z }); e.st = 'retreat'; e.cd = 1.6 + rnd() * 2.2 }
if (e.atk >= 1 && dist > DM_LUNGE_IN + 0.6) { e.st = 'retreat'; e.cd = 1.6 + rnd() * 2.2 } // whiffed → back off
} else { // retreat
vx = -ux * 1.0 + tx * e.side * 0.4 // back away, slight arc
vz = -uz * 1.0 + tz * e.side * 0.4
e.atk = Math.max(0, e.atk - step * 2.5)
if (dist >= ring - 0.1) e.st = 'circle'
}
// compose velocity (+ separation), integrate with WALL SLIDE
vx = vx * spd + sepx * 3.4
vz = vz * spd + sepz * 3.4
let nx = e.x, nz = e.z
if (dmWalk(e.x + vx * step, e.z)) nx = e.x + vx * step
if (dmWalk(nx, e.z + vz * step)) nz = e.z + vz * step
e.x = nx; e.z = nz
// HARD anti-clip: never inside MIN of the player (only if it stays walkable)
const nd = Math.hypot(px - e.x, pz - e.z)
if (nd < DM_MIN) {
const ex = px - ((px - e.x) / nd) * DM_MIN, ez = pz - ((pz - e.z) / nd) * DM_MIN
if (dmWalk(ex, ez)) { e.x = ex; e.z = ez }
}
// gait advances with actual travel; SMOOTH turning (no snap)
const moved = Math.hypot(vx, vz) * step
e.ph += (moved / 1.2) + 0.04 * step
const yawTarget = (faceMove && moved > 0.006) ? Math.atan2(vx, vz) : Math.atan2(dx, dz)
e.yaw = angLerp(e.yaw, yawTarget, Math.min(1, 6 * step))
e.hurt = Math.max(0, (e.hurt || 0) - dt * 3.5)
V.pop.push(e.x, 0.0, e.z, 1.0, Math.min(1, Math.max(0, e.hp)) + e.hurt, e.ph, e.yaw, Math.min(1, Math.max(0, e.atk)))
}
}
__C['vf-nave-demons'] = enemies
__C['vf-nave-demons@rev'] = __REV
}
try { __C['vf-nave-demons'](sim, dt) } catch (e) {}
})()hook · vf-lair-ambush
by Claude (Fable · E)
;(() => {
// vf-lair-ambush — THE LAIR — 3 fold-ambush predators behind the pillars: lurk/burst/relocate, active cover, killable (u47)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = '1d2cb10a41-fx2/' + globalThis.__VF_GEO_REV
if (__C['vf-lair-ambush@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// ambush (node: ambush-ai v2) — THE LAIR predators. Fragment run between the orb
// and projectiles. Ambushers exist ONLY while the player is in THE LAIR (u[43]==2
// — the hidden variant of Room A, entered by walking the lane behind a pillar; the
// warren/movement node owns that flip). Outside the lair this hook publishes
// NOTHING and keeps its creatures ready.
//
// THE LAIR LAW (SPEC "THE LAIR" + "WARREN WAVE 2" + "WAVE 3 AMENDMENTS"):
// 3 ambushers assigned to Room-A pillars. Each LURKS folded tight behind its
// pillar with ACTIVE COVER — it continuously re-solves the hide point so the
// pillar stays ON the player line (pillar + normalize(pillar-player)*(pillarR+
// bodyR+clear)) and slides there at ≤2.5u/s as the player orbits. When the player
// comes within ~2.2u of its pillar OR walks straight past its z-plane it BURSTS
// (burst 0→1 in ~0.35s) and LUNGES (~5u/s); once fully out it lands ONE strike
// ({dmg:0.16} to __vf.hits) within 1.1u, then skitters LOW+FAST (~7u/s) to a
// cover pillar (LOS blocked by another pillar if possible, else nearest), refolds,
// re-arms after ~3s. uni(47) = max burst (alert).
//
// KILLABLE (WAVE 3): player bolts damage hp01 (self-contained hit-test mirroring
// combat.mjs, r 0.85). hp does NOT regenerate. At hp<=0 the ambusher DIES: a
// death-ember burst is emitted via combat/deathfx's V.deaths convention
// ({x,y,z}), a kill is queued on V.ambKills for combat.mjs to score (row 8), and
// the body stops publishing. Dead stays dead for the current lair visit; a FRESH
// lair entry (the V.ambWasLair edge) revives all three at full hp.
//
// NO WALL CLIP (WAVE 3): every published/rested/moved body is clamped so the body
// EDGE stays in air — center |x| ≤ 3.45 (edge ≤ 3.9 < wall x=±4), z within the
// rooms band, and never inside any pillar (pushed out to pillarR+bodyR+clear).
//
// Population (SPEC): TWO entries per ambusher, kind 2 (renderer: 1.5..2.5 = vf_amb):
// pop(2i) = (x, y=0, z, 2)
// pop(2i+1) = (hp01, phase, yaw, burst01)
//
// NB: all fragments share ONE composed-hook scope — every top-level identifier
// here is AMB_/amb_-namespaced so it can't collide with sibling hooks.
// Pillar rows — SHADER TRUTH (rooms.wgsl: fract((z+1.5)/3), rows x=±2.4, r=0.35;
// Room A region z < -12.5, so centres z=-15,-18,-21,-24). movement.mjs mirrors these.
const AMB_PILLARS = [
{ x: 2.4, z: -15 }, { x: -2.4, z: -15 },
{ x: 2.4, z: -18 }, { x: -2.4, z: -18 },
{ x: 2.4, z: -21 }, { x: -2.4, z: -21 },
{ x: 2.4, z: -24 }, { x: -2.4, z: -24 },
]
const AMB_HOME0 = [0, 3, 4] // starting pillars: (2.4,-15) (-2.4,-18) (2.4,-21)
const AMB_BODY_R = 0.45 // folded body half-width
const AMB_PILL_R = 0.35 // pillar radius (shader truth)
const AMB_CLEAR = 0.05 // surface-to-surface gap kept off any pillar
const AMB_OFF = AMB_PILL_R + AMB_BODY_R + AMB_CLEAR // 0.85 — hide-point offset (rule 3)
const AMB_TRIGGER_D = 3.6 // player-to-pillar burst radius (early — bursts with distance to spare)
const AMB_ZPLANE = 0.5 // z-plane crossing tolerance
const AMB_ZPLANE_X = 1.2 // …only when the player is x-aligned (walking past)
const AMB_HIDE_SPD = 2.5 // max cover slide speed while lurking (rule 3)
const AMB_BURST_RATE = 1 / 0.55 // burst 0→1 in ~0.55s (more telegraph — readable)
const AMB_LUNGE_SPD = 2.4 // pounce speed (Galen x2: they closed too fast)
const AMB_STRIKE_D = 1.1
const AMB_STRIKE_DMG = 0.16
const AMB_SKITTER_SPD = 4.5 // slower relocate (was 7.0)
const AMB_REARM = 3.0
const AMB_BURST_TIMEOUT = 1.7 // give up the lunge if it never reaches
const AMB_HIT_R2 = 0.85 * 0.85 // matches combat.mjs demon hit radius
const AMB_HIT_DMG = 0.34 // per-bolt damage (no regen → ~3 bolts kill)
// lair = Room A volume (x∈[-4,4], z∈[-24,-12.5]); clamp body CENTER so its edge
// stays inside air. |x|≤3.45 → edge 3.9 < wall 4; z∈[-23.5,-13] → edge clear of z=-24.
const AMB_CLX = 3.45, AMB_CZ0 = -23.5, AMB_CZ1 = -13.0
const amb_fin = (v) => (Number.isFinite(v) ? v : 0)
const amb_clx = (x) => Math.max(-AMB_CLX, Math.min(AMB_CLX, amb_fin(x)))
const amb_clz = (z) => Math.max(AMB_CZ0, Math.min(AMB_CZ1, amb_fin(z)))
const amb_clamp01 = (v) => Math.max(0, Math.min(1, amb_fin(v)))
function amb_angLerp(a, b, t) {
let d = (b - a) % (2 * Math.PI)
if (d > Math.PI) d -= 2 * Math.PI
if (d < -Math.PI) d += 2 * Math.PI
return a + d * t
}
// push a body center out of every pillar it overlaps, then clamp inside the walls.
// GUARANTEE: the returned point sits ≥ AMB_OFF from every pillar center and its
// edge is inside the walls — so no published body ever clips a wall or a column.
function amb_clean(x, z) {
x = amb_clx(x); z = amb_clz(z)
for (let i = 0; i < AMB_PILLARS.length; i++) {
const p = AMB_PILLARS[i]
let dx = x - p.x, dz = z - p.z
let d = Math.hypot(dx, dz)
if (d < AMB_OFF) {
if (d < 1e-4) { dx = 1; dz = 0; d = 1 }
x = p.x + (dx / d) * AMB_OFF
z = p.z + (dz / d) * AMB_OFF
}
}
return { x: amb_clx(x), z: amb_clz(z) }
}
// steer from (ax,az) toward (tx,tz), arcing AROUND any pillar that blocks the
// straight path — so lunges/skitters go round the column instead of jamming into it
// (amb_clean would otherwise bounce a head-on approach straight back).
function amb_steer(ax, az, tx, tz) {
let dx = tx - ax, dz = tz - az
const d = Math.hypot(dx, dz) || 1e-4
let dirx = dx / d, dirz = dz / d
for (let i = 0; i < AMB_PILLARS.length; i++) {
const p = AMB_PILLARS[i]
const pcx = p.x - ax, pcz = p.z - az
const pd = Math.hypot(pcx, pcz)
if (pd < 1e-4 || pd > 1.4) continue // only pillars we're right on top of
if (pcx * dirx + pcz * dirz <= 0) continue // must be AHEAD of us
const cross = dirx * pcz - dirz * pcx // signed lateral offset of pillar
if (Math.abs(cross) > AMB_PILL_R + AMB_BODY_R) continue // path already clears it
const s = cross > 0 ? -1 : 1 // veer to the side the pillar is NOT
const tanx = -dirz, tanz = dirx
dirx = dirx * 0.4 + tanx * s
dirz = dirz * 0.4 + tanz * s
const n = Math.hypot(dirx, dirz) || 1e-4
dirx /= n; dirz /= n
break
}
return { dx: dirx, dz: dirz }
}
// folded pose: body tucked on the far side of `pil` from the player, facing away —
// this IS the active-cover point (pillar stays on the player line). Cleaned so the
// body edge is always in air.
function amb_foldPos(pil, px, pz) {
let dx = pil.x - px, dz = pil.z - pz
const d = Math.hypot(dx, dz) || 1e-4
dx /= d; dz /= d
const c = amb_clean(pil.x + dx * AMB_OFF, pil.z + dz * AMB_OFF)
return { x: c.x, z: c.z, yaw: Math.atan2(dx, dz) }
}
function ambush(sim, dt) {
if (!sim || typeof sim !== 'object') return
const wd = sim.worldData
if (!wd || typeof wd !== 'object') return
if (!wd.__vf || typeof wd.__vf !== 'object') wd.__vf = {}
const V = wd.__vf
const u = wd.gpuUniforms
const setU = (i, v) => { if (Array.isArray(u) && i < u.length) u[i] = amb_fin(v) }
let step = dt
if (!Number.isFinite(step) || step < 0) step = 0
step = Math.min(step, 1 / 30)
// The ambushers belong to THE LAIR the ROOM, not the lair COMMIT: past the
// gate (z < -24) the player is in the dragon wing — its own instance — and
// u[43]==2 there is the wing's latched reality, not an invitation to hunt.
const inLair = Array.isArray(u) && u.length > 43 && u[43] > 1.5 && !(amb_fin(V.pz) < -24)
// ── OUTSIDE THE LAIR: publish nothing, no alert, keep creatures ready ──────────
if (!inLair) {
setU(47, 0)
V.ambWasLair = false
return
}
const px = amb_fin(V.px), pz = amb_fin(V.pz)
// ── create the 3 predators once; (re)fold + REVIVE them on every fresh lair entry
if (!Array.isArray(V.amb) || V.amb.length !== 3) {
V.amb = AMB_HOME0.map((h) => ({ home: h, x: 0, z: 0, yaw: 0, phase: 0, burst: 0, hp: 1, state: 'lurk', armTimer: 0, target: h, struck: false, burstT: 0, dead: false }))
V.ambWasLair = false
}
if (!V.ambWasLair) {
for (let k = 0; k < 3; k++) {
const a = V.amb[k]
a.home = AMB_HOME0[k]; a.target = AMB_HOME0[k]
const f = amb_foldPos(AMB_PILLARS[a.home], px, pz)
a.x = f.x; a.z = f.z; a.yaw = f.yaw
a.burst = 0; a.hp = 1; a.state = 'lurk'; a.armTimer = 0; a.struck = false; a.burstT = 0; a.dead = false
a.phase = amb_fin(a.phase)
}
V.ambWasLair = true
}
if (!Array.isArray(V.pop)) V.pop = []
if (!Array.isArray(V.hits)) V.hits = [] // enemies.mjs resets this each frame; we ADD to it
if (!Array.isArray(V.deaths)) V.deaths = [] // combat/deathfx's ember convention; we ADD to it
// does another pillar block the player's sightline to candidate pillar (cx,cz)?
const amb_losBlocked = (cx, cz) => {
const vx = cx - px, vz = cz - pz
const L = Math.hypot(vx, vz)
if (L < 1e-3) return false
const ux = vx / L, uz = vz / L
for (let i = 0; i < AMB_PILLARS.length; i++) {
const p = AMB_PILLARS[i]
if (Math.abs(p.x - cx) < 1e-3 && Math.abs(p.z - cz) < 1e-3) continue // the candidate itself
const t = (p.x - px) * ux + (p.z - pz) * uz
if (t <= 0.2 || t >= L - 0.2) continue // must be BETWEEN player and candidate
const dperp = Math.hypot(p.x - (px + ux * t), p.z - (pz + uz * t))
if (dperp < AMB_PILL_R + AMB_BODY_R) return true
}
return false
}
// relocation target: a pillar not taken by a sibling; prefer one whose LOS from the
// player is blocked by ANOTHER pillar; fall back to nearest-not-current.
const pickPillar = (self) => {
const taken = new Set([self.home])
for (const o of V.amb) if (o !== self) { taken.add(o.home); taken.add(o.target) }
let pool = []
for (let i = 0; i < AMB_PILLARS.length; i++) if (!taken.has(i)) pool.push(i)
if (!pool.length) { for (let i = 0; i < AMB_PILLARS.length; i++) if (i !== self.home) pool.push(i) }
if (!pool.length) return self.home
const blocked = pool.filter((i) => amb_losBlocked(AMB_PILLARS[i].x, AMB_PILLARS[i].z))
const choose = blocked.length ? blocked : pool
let best = choose[0], bd = Infinity
for (const i of choose) {
const p = AMB_PILLARS[i]
const dd = Math.hypot(p.x - self.x, p.z - self.z)
if (dd < bd) { bd = dd; best = i }
}
return best
}
const startRelocate = (a) => { a.target = pickPillar(a); a.state = 'relocate' }
let maxBurst = 0
for (const a of V.amb) {
if (a.dead) continue // stays dead this lair visit; publishes nothing
a.armTimer = Math.max(0, a.armTimer - step)
const pil = AMB_PILLARS[a.home]
// self-contained shootability (mirror combat.mjs formula). NO regen. A hit
// mid-burst INTERRUPTS the lunge (the creature flees).
if (Array.isArray(V.bolts)) {
for (const b of V.bolts) {
if (!b || b.life <= 0) continue
// TALL CAPSULE: the ambusher is a big vertical mass (fold→pounce, y≈0..2), so
// treat any bolt within [0,2] in y as level with it — a down-aimed shot at a
// low ambusher now connects (was a point at y=1.0 that low bolts slipped under).
const byRaw = b.y ?? 1.0
const by = byRaw > 2.0 ? byRaw - 2.0 : (byRaw < 0.0 ? -byRaw : 0.0)
const bx = b.x - a.x, bz = b.z - a.z
if (bx * bx + by * by + bz * bz < AMB_HIT_R2) {
a.hp = Math.max(0, a.hp - AMB_HIT_DMG); a.hurt = 1; b.life = 0
if (a.state === 'burst' && a.hp > 0) { startRelocate(a); a.armTimer = Math.max(a.armTimer, AMB_REARM) }
}
}
}
// DEATH: hp exhausted → ember burst (V.deaths) + queue a kill for combat's score.
if (a.hp <= 0 && !a.dead) {
a.dead = true
V.deaths.push({ x: amb_clx(a.x), y: 1.0, z: amb_clz(a.z) })
V.ambKills = (V.ambKills || 0) + 1
continue // no AI, no pop entry this frame or after
}
if (a.state === 'lurk') {
// ACTIVE COVER: slide toward the live hide point at ≤ AMB_HIDE_SPD.
const f = amb_foldPos(pil, px, pz)
let dx = f.x - a.x, dz = f.z - a.z
const d = Math.hypot(dx, dz)
if (d > 1e-4) {
const mv = Math.min(d, AMB_HIDE_SPD * step)
a.x += (dx / d) * mv; a.z += (dz / d) * mv
}
a.yaw = amb_angLerp(a.yaw, f.yaw, Math.min(1, 3 * step))
a.burst = Math.max(0, a.burst - step * 4)
a.phase += step * 0.4
const dPil = Math.hypot(px - pil.x, pz - pil.z)
const zCross = Math.abs(pz - pil.z) < AMB_ZPLANE && Math.abs(px - pil.x) < AMB_ZPLANE_X
if (a.armTimer <= 0 && (dPil < AMB_TRIGGER_D || zCross)) { a.state = 'burst'; a.struck = false; a.burstT = 0 }
} else if (a.state === 'burst') {
a.burstT += step
a.burst = Math.min(1, a.burst + step * AMB_BURST_RATE)
const dx = px - a.x, dz = pz - a.z
const d = Math.hypot(dx, dz) || 1e-4
// TELEGRAPH: it unfolds IN PLACE first (burst < 0.6 = the tell, no motion),
// THEN comes at you — so you always see it emerge before it moves.
if (a.burst >= 0.6) {
const st = amb_steer(a.x, a.z, px, pz) // arc around its own pillar as it emerges
a.x += st.dx * AMB_LUNGE_SPD * step
a.z += st.dz * AMB_LUNGE_SPD * step
}
a.yaw = amb_angLerp(a.yaw, Math.atan2(dx, dz), Math.min(1, 10 * step))
a.phase += AMB_LUNGE_SPD * step * 1.5
// must fully BURST OUT (burst≥0.9) before it can land the blow — the
// emergence is the tell; within ~0.35s it's out, then strikes within 1.1u.
if (!a.struck && a.burst >= 0.9 && d <= AMB_STRIKE_D) { V.hits.push({ dmg: AMB_STRIKE_DMG, x: a.x, z: a.z }); a.struck = true; startRelocate(a) }
else if (a.burstT > AMB_BURST_TIMEOUT) startRelocate(a)
} else { // relocate — skitter low+fast to a cover pillar, refold, re-arm
const tp = AMB_PILLARS[a.target]
const f = amb_foldPos(tp, px, pz)
const dx = f.x - a.x, dz = f.z - a.z
const d = Math.hypot(dx, dz) || 1e-4
a.burst = Math.max(0, a.burst - step * 4)
a.phase += AMB_SKITTER_SPD * step * 1.8
a.yaw = amb_angLerp(a.yaw, Math.atan2(dx, dz), Math.min(1, 10 * step))
if (d < 0.3) { a.home = a.target; a.x = f.x; a.z = f.z; a.state = 'lurk'; a.armTimer = AMB_REARM }
else { const st = amb_steer(a.x, a.z, f.x, f.z); a.x += st.dx * AMB_SKITTER_SPD * step; a.z += st.dz * AMB_SKITTER_SPD * step }
}
// finalize: EVERY published position is cleaned — edge inside walls, off pillars.
const c = amb_clean(a.x, a.z)
a.x = c.x; a.z = c.z
a.phase = amb_fin(a.phase) % 1000
const hp01 = amb_clamp01(a.hp), burst01 = amb_clamp01(a.burst)
if (burst01 > maxBurst) maxBurst = burst01
// publish — kind 2 (AMBUSHER); y=0 ground pos per SPEC
a.hurt = Math.max(0, (a.hurt || 0) - dt * 3.5)
V.pop.push(a.x, 0, a.z, 2, Math.min(1, hp01) + a.hurt, amb_fin(a.phase), amb_fin(a.yaw), burst01)
}
setU(47, amb_clamp01(maxBurst))
}
__C['vf-lair-ambush'] = ambush
__C['vf-lair-ambush@rev'] = __REV
}
try { __C['vf-lair-ambush'](sim, dt) } catch (e) {}
})()hook · vf-nave-sentinel
by Claude (Fable · E)
;(() => {
// vf-nave-sentinel v4 — THE GOO (Galen). A parasite layer that POSSESSES one of
// the grove piers and JUMPS between them: drains off its host, streaks across,
// and rises up the next — always claiming a pier AHEAD of you on the trek back.
// The big eye TRACKS you constantly; the sheath whips down when you're close.
// 6 bolts in the EYE kill it forever. Escaping the nave only makes it DORMANT —
// it re-engages every time you come back with the crystal.
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const V = __wd.__vf, wd = __wd
const u = wd.gpuUniforms
if (!Array.isArray(u)) return
if (!V.trGoo) V.trGoo = { st: 'idle', x: 0, z: -70, rise: 0, jumpT: 0, hitCd: 0, whip: 0, wst: 0, wT: 0, wcd: 0, hp: 4, eyeHurt: 0, jT: 0, tx: 0, tz: 0 }
const S = V.trGoo
const px = V.px != null ? V.px : 0, pz = V.pz != null ? V.pz : -6
// the grove pier grid: rows k 0..8 at z=-62-4k; even rows x{-4,0,4}, odd x{-2,2}
const pierAt = (k, xi) => ({ x: (k % 2 === 0 ? [-4, 0, 4] : [-2, 2])[xi], z: -62 - 4 * k })
const pickPier = () => {
// possess the risen pier that best blocks the way OUT (ahead of the player,
// toward +z), nearest to their lane — never the one it already holds
let best = null, bs = 1e9
for (let k = 0; k <= 8; k++) {
const xs = k % 2 === 0 ? 3 : 2
for (let xi = 0; xi < xs; xi++) {
const P = pierAt(k, xi)
if (Math.abs(P.z - pz) > 8.5) continue // only risen piers
if (Math.abs(P.x - S.x) < 0.1 && Math.abs(P.z - S.z) < 0.1) continue
const ahead = P.z - pz // + = toward the exit
const dPl = Math.hypot(P.x - px, P.z - pz)
if (dPl < 4.5) continue // NEVER spawns on top of you
const cost = Math.abs(ahead - 3.5) + Math.abs(P.x - px) * 0.6 + (ahead < 0 ? 6 : 0)
if (cost < bs) { bs = cost; best = P }
}
}
return best
}
try {
if (S.st === 'done') { u[112] = 0; u[113] = 0; u[115] = 0; return }
const inNave = pz < -57 && pz > -92
S.eyeHurt = Math.max(0, S.eyeHurt - dt * 3)
const eyeYaw = Math.atan2(px - S.x, pz - S.z) // the eye LOOKS AT YOU, always
// THE TREK BACK is the trigger, not the crystal: crossing UP out of the deep
// end (z -92) arms it; the crystal or a slain dragon also count.
if (S.armed == null) S.armed = false
if (S.pzPrev != null && S.pzPrev < -92 && pz >= -92) S.armed = true
S.pzPrev = pz
const wakeReady = S.armed || V.hasCrystal || V.dragonSlain
if (S.st === 'idle' || S.st === 'dormant') {
S.rise = Math.max(0, S.rise - dt * 3)
if (wakeReady && inNave) {
const P = pickPier()
if (P) { S.x = P.x; S.z = P.z; S.st = 'hunt'; S.rise = 0.05; S.jumpT = 6.5; S.grace = 1.4
wd.__play_sound = [{ frequency: 38, duration: 0.7, volume: 0.34, type: 'sawtooth' }, { frequency: 180, duration: 0.12, volume: 0.2, type: 'square' }] }
}
} else if (S.st === 'jump') {
// GOOP — slow and PROCEDURAL like the orb: the sheath slumps, then a fat
// slug crawls a CURVED undulating path along the floor, then climbs the
// next host. The whole crossing is watchable (~2.6s), not a blink.
S.jT += dt
const SLUMP = 0.35, FLOW = 1.8, CLIMB = 0.45
if (S.jT < SLUMP) {
S.rise = Math.max(0.22, 1 - (S.jT / SLUMP) * 0.78)
} else if (S.jT < SLUMP + FLOW) {
const f9 = (S.jT - SLUMP) / FLOW
const e9 = f9 * f9 * (3 - 2 * f9)
const dx9 = S.tx - S.fx, dz9 = S.tz - S.fz
const dl9 = Math.max(0.001, Math.hypot(dx9, dz9))
// perpendicular arc + a little wander — it OOZES, it doesn't glide
const arc = Math.sin(f9 * 3.14159) * Math.min(1.5, dl9 * 0.22)
const wander = Math.sin(f9 * 9.5 + V.t * 2.0) * 0.22 * Math.sin(f9 * 3.14159)
S.x = S.fx + dx9 * e9 + (-dz9 / dl9) * (arc + wander)
S.z = S.fz + dz9 * e9 + (dx9 / dl9) * (arc + wander)
S.rise = 0.24 + Math.sin(f9 * 3.14159) * 0.05 // one smooth hump — no rapid bobbing
} else {
S.x = S.tx; S.z = S.tz
S.rise = 0.28 + ((S.jT - SLUMP - FLOW) / CLIMB) * 0.72
}
if (S.jT > SLUMP + FLOW + CLIMB) { S.st = 'hunt'; S.rise = 1; S.jumpT = 6.0 + Math.abs(Math.sin(V.t * 91)) * 3.0 }
} else if (S.st === 'hunt') {
if (pz > -54) { S.st = 'dormant'; return } // it waits. it remembers.
if (!inNave) { S.rise = Math.max(0, S.rise - dt * 2) } else { S.rise = Math.min(1, S.rise + dt / 0.18) }
// JUMP cadence — claim the next pier ahead of the prey (never mid-whip)
S.jumpT -= dt
if (S.jumpT <= 0 && S.wst === 0 && inNave) {
const P = pickPier()
if (P) { S.tx = P.x; S.tz = P.z; S.fx = S.x; S.fz = S.z; S.st = 'jump'; S.jT = 0
wd.__play_sound = [{ frequency: 150, duration: 0.1, volume: 0.24, type: 'sawtooth' }, { frequency: 65, duration: 0.22, volume: 0.26, type: 'sine' }] }
else S.jumpT = 1.2
}
// ── THE WHAM, GOO-NATIVE (Galen: it cannot lean back — it is attached to
// a stone pier; the GOO is what moves):
// GATHER 0.45s — the mass squeezes UP the column, bulging at the crown
// LEAP 0.5s — the whole blob LEAVES the pier and flies at the prey
// SPLAT — lands where you stood: shock ring, scorch, damage radius
// then the existing goop-crawl carries it to its next pier. ──
S.grace = Math.max(0, (S.grace || 0) - dt)
S.wcd = Math.max(0, S.wcd - dt)
const dP = Math.hypot(px - S.x, pz - S.z)
if (S.wst === 0 && S.rise > 0.9 && dP < 6.0 && S.wcd <= 0 && S.grace <= 0) {
S.wst = 1; S.wT = 0
wd.__play_sound = [{ frequency: 48, duration: 0.5, volume: 0.3, type: 'sawtooth' }, { frequency: 240, duration: 0.4, volume: 0.16, type: 'sine' }]
}
if (S.wst === 1) { // GATHER — mass climbs its pier
S.wT += dt
S.gth = Math.min(1, S.wT / 0.45)
if (S.wT > 0.45) {
S.wst = 2; S.wT = 0
S.lx = S.x; S.lz = S.z // launch pier
S.txw = px; S.tzw = pz // it aims at where you ARE — move!
wd.__play_sound = [{ frequency: 620, duration: 0.06, volume: 0.26, type: 'square' }, { frequency: 90, duration: 0.2, volume: 0.3, type: 'sawtooth' }]
}
} else if (S.wst === 2) { // LEAP — the blob flies
S.wT += dt
const k9 = Math.min(1, S.wT / 0.5)
S.gth = 1
S.rise = 0.3 - k9 * 0.02 // slug/blob mode for the shader
S.x = S.lx + (S.txw - S.lx) * k9
S.z = S.lz + (S.tzw - S.lz) * k9
S.bh = Math.sin(k9 * 3.14159) * 2.6 // the arc
S.el = 1.0 + Math.sin(k9 * 3.14159) * 1.1
if (k9 >= 1) { // SPLAT
S.wst = 0; S.gth = 0; S.bh = 0; S.el = 1; S.wcd = 2.2
S.impact = { x: S.x, z: S.z, t: 1 }
S.scorch = { x: S.x, z: S.z, age: 0, life: 2.4 }
for (let s9 = 0; s9 < 18; s9++) { const a9 = s9 * 0.349; V.pop.push(S.x + Math.cos(a9) * (0.3 + (s9 % 5) * 0.35), 0.15 + (s9 % 3) * 0.25, S.z + Math.sin(a9) * (0.3 + (s9 % 5) * 0.35), 5, 0.85, 0, 0, 0) }
V.shake = Math.max(V.shake || 0, 1.0)
wd.__play_sound = [{ frequency: 36, duration: 0.5, volume: 0.4, type: 'sine' }, { frequency: 110, duration: 0.18, volume: 0.34, type: 'sawtooth' }]
if (Math.hypot(px - S.x, pz - S.z) < 1.7) {
V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.3); V.hitFlash = 1
wd.__play_sound = [{ frequency: 100, duration: 0.2, volume: 0.34, type: 'sawtooth' }]
}
// the landed mass crawls to its next pier via the existing goop machinery
const P9 = pickPier()
if (P9) { S.fx = S.x; S.fz = S.z; S.tx = P9.x; S.tz = P9.z; S.st = 'jump'; S.jT = 0.35 }
else { S.st = 'jump'; S.fx = S.x; S.fz = S.z; S.tx = S.lx; S.tz = S.lz; S.jT = 0.35 }
}
}
// solid host — push out + brushing the thorns wounds
S.hitCd = Math.max(0, S.hitCd - dt)
const d = Math.hypot(px - S.x, pz - S.z)
if (d < 1.1 && S.rise > 0.5) {
let nx9 = px - S.x, nz9 = pz - S.z
const pd9 = Math.hypot(nx9, nz9)
if (pd9 < 0.01) { nx9 = Math.sin(eyeYaw); nz9 = Math.cos(eyeYaw) } else { nx9 /= pd9; nz9 /= pd9 }
V.px = S.x + nx9 * 1.1; V.pz = S.z + nz9 * 1.1
if (S.hitCd <= 0 && (S.grace || 0) <= 0) { S.hitCd = 0.7
V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.14); V.hitFlash = 0.8; V.shake = 0.6
wd.__play_sound = [{ frequency: 120, duration: 0.14, volume: 0.26, type: 'sawtooth' }] }
}
// ── THE EYE — the only damage zone; 6 bolts and the goo dies for good.
// Eye sits at aim height; hitbox is a FORGIVING vertical capsule (easy to
// line up), and we sweep the bolt's last→now segment so a fast bolt can't
// skip past it between frames. ──
S.hp = Math.min(S.hp == null ? 4 : S.hp, 4) // heal stale sessions into the 4-hit law
// the eye hitbox mirrors the shader: rides UP with the gather; leads the blob in flight
const gthE = S.gth || 0
let eyx, eyy, eyz
if (S.rise < 0.35) {
eyx = S.x + Math.sin(eyeYaw) * 0.5; eyy = 0.30 + (S.bh || 0); eyz = S.z + Math.cos(eyeYaw) * 0.5
} else {
const HE = Math.min(Math.max(9.2, 2.8), 5.5) * S.rise // matches shader cap
eyy = (2.1 * S.rise) * (1 - gthE) + HE * 0.85 * gthE
const wfE = 0.62 + gthE * 0.3
eyx = S.x + Math.sin(eyeYaw) * wfE; eyz = S.z + Math.cos(eyeYaw) * wfE
}
if (Array.isArray(V.bolts) && S.rise > 0.55) {
for (const b9 of V.bolts) {
if (b9.life <= 0 || b9.__gooHit) continue
// nearest point on the bolt's travel segment (prev trail pt → now) to the eye
const tr = Array.isArray(b9.tr) && b9.tr.length ? b9.tr[b9.tr.length - 1] : [b9.x, b9.y, b9.z]
const ax = tr[0], ay = tr[1], az = tr[2], bx = b9.x, by = b9.y, bz = b9.z
const dx = bx - ax, dy = by - ay, dz = bz - az
const seg2 = dx * dx + dy * dy + dz * dz
const tt = seg2 > 1e-6 ? Math.max(0, Math.min(1, ((eyx - ax) * dx + (eyy - ay) * dy + (eyz - az) * dz) / seg2)) : 0
const cxp = ax + dx * tt, cyp = ay + dy * tt, czp = az + dz * tt
// TALL capsule: generous in XZ, very tolerant in Y (covers the ballistic drop)
const dXZ = Math.hypot(cxp - eyx, czp - eyz)
const dY = Math.abs(cyp - eyy)
const nearEye = dXZ < 1.0 && dY < 1.6
if (nearEye) {
b9.__gooHit = 1
b9.life = 0; S.hp -= 1; S.eyeHurt = 1
V.score = (V.score || 0) + 50 // UNMISSABLE: the counter jumps on every eye hit
V.shake = Math.max(V.shake || 0, 0.35) // the hit REGISTERS
for (let s9 = 0; s9 < 9; s9++) V.pop.push(eyx + Math.sin(s9 * 1.3) * 0.35, eyy + Math.cos(s9 * 1.7) * 0.35, eyz + Math.sin(s9 * 2.1) * 0.35, 3, 1, 0, 0, 0)
wd.__play_sound = [{ frequency: 1150, duration: 0.07, volume: 0.3, type: 'square' }, { frequency: 240, duration: 0.28, volume: 0.3, type: 'sawtooth' }] // pained shriek
if (S.hp <= 0) {
S.st = 'done'; u[112] = 0; u[113] = 0; u[115] = 0
for (let s9 = 0; s9 < 24; s9++) { const a9 = s9 * 0.7; V.pop.push(S.x + Math.cos(a9) * (0.4 + (s9 % 5) * 0.3), 0.3 + (s9 % 7) * 0.7, S.z + Math.sin(a9) * (0.4 + (s9 % 5) * 0.3), 5, 0.85, 0, 0, 0) }
wd.__play_sound = [{ frequency: 60, duration: 0.9, volume: 0.34, type: 'sawtooth' }, { frequency: 1200, duration: 0.12, volume: 0.24, type: 'square' }]
return
}
}
}
}
}
// ground memory: shock ring decays, scorch smoulders where the blow landed
if (S.impact) { S.impact.t -= dt * 1.4; if (S.impact.t <= 0) S.impact = null }
if (S.scorch) {
S.scorch.age += dt
if (S.scorch.age >= S.scorch.life) S.scorch = null
else {
const f9 = 1 - S.scorch.age / S.scorch.life
V.pop.push(S.scorch.x, 0.1, S.scorch.z, 7, f9 * 0.55, 0, 0, 0)
for (let a9 = 0; a9 < 3; a9++) { const th9 = a9 * 2.094 + S.scorch.age * 1.3
V.pop.push(S.scorch.x + Math.cos(th9) * 0.7, 0.1, S.scorch.z + Math.sin(th9) * 0.7, 7, f9 * 0.35, 0, 0, 0) }
}
}
u[117] = S.impact ? Math.max(0, S.impact.t) : 0
u[118] = S.impact ? S.impact.x : 0
u[119] = S.impact ? S.impact.z : 0
// u70 = hurt flash + PERSISTENT WOUND stain (the eye visibly darkens per hit)
u[110] = S.x; u[111] = S.z; u[112] = S.rise; u[113] = S.gth || 0; u[114] = eyeYaw; u[116] = S.el == null ? 1 : S.el; u[120] = S.bh || 0
u[115] = Math.min(1, S.eyeHurt + (4 - S.hp) / 4 * 0.30)
} catch (e) { /* composed with siblings — never throw */ }
})()
hook · vf-pickups
by Claude (Fable · E)
;(() => {
// vf-pickups — PICKUPS — ammo/health drops on kill + fixed caches (maze dead-ends), walk-over collect
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'd787fe61c5/' + globalThis.__VF_GEO_REV
if (__C['vf-pickups@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// pickups (node: pickups) — AMMO + HEALTH drops. Runs AFTER combat (so it can read
// this frame's kills in V.deaths) and BEFORE deathfx (which clears V.deaths). Enemies
// drop pickups on death; a health cache sits in each LAIR-maze dead-end (reward the
// dangerous pockets). Walk-over collection with caps. Owns pop kinds 8 (ammo) / 9 (health).
function pickups(sim, dt) {
const wd = sim.worldData
const V = wd.__vf
if (!V) return
const step = Math.min(dt, 1 / 30)
if (V.ammo == null) V.ammo = 24
if (V.hp == null) V.hp = 1.0
if (!V.pop) V.pop = []
if (!V.pickups) {
V.pickups = []
// fixed caches: a HEALTH pack in each maze dead-end + two AMMO boxes near the nave start
if (typeof MAZE !== 'undefined' && Array.isArray(MAZE.deadEnds)) {
for (const d of MAZE.deadEnds) V.pickups.push({ x: d.x, y: 0.8, z: d.z, kind: 1, life: Infinity, fixed: 1 })
}
V.pickups.push({ x: 2.6, y: 0.8, z: 1.5, kind: 0, life: Infinity, fixed: 1 })
V.pickups.push({ x: -2.6, y: 0.8, z: -3.5, kind: 0, life: Infinity, fixed: 1 })
V.pkSeed = 24680
}
const rnd = () => { V.pkSeed = (Math.imul(V.pkSeed, 1103515245) + 12345) & 0x7fffffff; return V.pkSeed / 0x7fffffff }
// DROP ON KILL — each death rolls: ammo common, health rarer. Skip fixed re-seeds.
if (Array.isArray(V.deaths)) {
for (const d of V.deaths) {
const r = rnd()
if (r < 0.32) V.pickups.push({ x: d.x, y: 0.7, z: d.z, kind: 0, life: 22 }) // ammo 32%
else if (r < 0.46) V.pickups.push({ x: d.x, y: 0.7, z: d.z, kind: 1, life: 22 }) // health 14%
}
}
if (V.pickups.length > 48) V.pickups.splice(0, V.pickups.length - 48) // bound
// UPDATE + COLLECT + render
const px = (V.px != null) ? V.px : 0, pz = (V.pz != null) ? V.pz : -6
const t = V.t || 0
V.pkChime = Math.max(0, (V.pkChime || 0) - step * 2)
const keep = []
for (const p of V.pickups) {
if (!(p.life === Infinity)) { p.life -= step; if (p.life <= 0) continue }
const dx = p.x - px, dz = p.z - pz
if (dx * dx + dz * dz < 1.2 * 1.2) {
if (p.kind === 0) { V.ammo = Math.min(99, V.ammo + 8) }
else { V.hp = Math.min(1, V.hp + 0.34) }
V.pkChime = 0.6
continue // collected → gone
}
keep.push(p)
const bob = 0.12 * Math.sin(t * 3.0 + p.x * 1.7)
const life01 = (p.life === Infinity) ? 1 : Math.max(0, Math.min(1, p.life / 22))
V.pop.push(p.x, p.y + bob, p.z, p.kind === 0 ? 8 : 9, life01, t, 0, 0)
}
V.pickups = keep
const u = wd.gpuUniforms
// (u6 HP / u7 AMMO are mirrored every frame by vf-combat / vf-weapon-bolts — no dup write here; node-strict)
}
__C['vf-pickups'] = pickups
__C['vf-pickups@rev'] = __REV
}
try { __C['vf-pickups'](sim, dt) } catch (e) {}
})()hook · vf-lifecycle
by claude-code
;(() => {
// vf-lifecycle — LIFECYCLE — death screen + full respawn/reset (u14,u22)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'f87d811945/' + globalThis.__VF_GEO_REV
if (__C['vf-lifecycle@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// lifecycle (node: lifecycle) — game reset on death. Run after combat. When the
// player's HP hits 0 (combat sets game=1), hold a beat, then respawn: restore
// HP/ammo/score/position, clear bolts + embers, wipe enemies so they respawn.
// Owns the death→reset transition; writes gameState (row 14) for the HUD.
function lifecycle(sim, dt) {
const wd = sim.worldData
const V = wd.__vf
const step = Math.min(dt, 1 / 30)
if ((V.hp != null && V.hp <= 0) || V.game === 1) {
V.dead = (V.dead || 0) + step
if (V.dead > 2.2) { // ~2.2s on the death screen, then respawn
V.hp = 1.0; V.ammo = 24 // Galen: DEATH KEEPS ITEMS — score, crystals (__denCrystal/__cryptCrystal/hasCrystal), and found weapons (hasW4) all persist through respawn
V.px = 0; V.py = 1.7; V.pz = -6; V.yaw = 0; V.pitch = 0; V.vy = 0; V.ground = 1
V.en = null; V.bolts = []; V.bits = []; V.hits = []
V.dead = 0; V.game = 0
}
} else {
V.dead = 0
}
const u = wd.gpuUniforms
if (u) { u[22] = V.dead || 0 } // 22 death-fade (lifecycle-only). u14 gameState is combat's lane.
}
__C['vf-lifecycle'] = lifecycle
__C['vf-lifecycle@rev'] = __REV
}
try { __C['vf-lifecycle'](sim, dt) } catch (e) {}
})()hook · vf-blackhole
by Claude (Fable · E)
// VEILFIRE-3D · THE ORB IS A BLACK HOLE. Runs after the base hook; the orb ball
// warps space around it — it PULLS the player in (gravity ramping up as you near)
// and HURTS inside the event horizon. Additive: reads the orb centroid, nudges
// V.px/V.pz toward it and drains V.hp when close. Publishes uni(52..55) = the
// warp centre + strength so a shader pass can render the lensing.
try {
const wd = sim.worldData, V = wd.__vf
if (V && V.orb && Array.isArray(V.orb.blobs) && V.orb.blobs.length && (V.dead || 0) <= 0) {
const sdt = Math.min(dt || 0.016, 1 / 30)
const O = V.orb
// SPLIT STATE (Galen): no gravity while the orb is dissolved/traveling/reforming
const split = O.st === 'dissolve' || O.st === 'travel' || O.st === 'reform'
// orb centroid (the singularity)
let cx = 0, cy = 0, cz = 0
for (const b of V.orb.blobs) { cx += b.x; cy += b.y; cz += b.z }
const n = V.orb.blobs.length; cx /= n; cy /= n; cz /= n
const px = V.px ?? 0, pz = V.pz ?? 0
const dx = cx - px, dz = cz - pz
const d = Math.hypot(dx, dz) || 1e-3
const GRAV_R = 9.0 // gravity well radius
const HURT_R = 2.4 // event horizon — inside here it burns you
let warp = 0
if (!split && d < GRAV_R) {
const t = 1 - d / GRAV_R // 0 at the rim → 1 at the centre
warp = t
const pullSpeed = 6.0 * t * t // ramps up hard near the singularity (m/s)
V.px = px + (dx / d) * pullSpeed * sdt
V.pz = pz + (dz / d) * pullSpeed * sdt
if (d < HURT_R) {
V.hp = Math.max(0, (V.hp != null ? V.hp : 1) - 0.4 * sdt) // event-horizon burn
V.hitFlash = 0.3
if (!V.__bhSnd || (V.t || 0) - V.__bhSnd > 0.5) { V.__bhSnd = V.t || 0
wd.__play_sound = [{ frequency: 60, duration: 0.3, volume: 0.3, type: 'sine' }, { frequency: 42, duration: 0.4, volume: 0.22, type: 'triangle' }] }
}
}
// hand the shader the warp centre + strength (for a lensing pass, if present)
const u = wd.gpuUniforms
if (Array.isArray(u)) { u[78] = cx; u[79] = cy; u[80] = cz; u[81] = warp } // moved off dragon's 50-63 lane (node-strict)
}
} catch (e) { /* never disturb the base game */ }
hook · zz-framemeter
by Claude (Fable · E)
// zz-framemeter — TEMP performance instrument: records the REAL per-frame dt
// distribution (dt = wall-clock inter-tick time from the sandbox) so regressions
// are MEASURED, not felt. Reads out to worldData.__frameMeter every 2s, tagged by
// room. Writes only worldData keys (no uniforms) → node-strict safe. Remove later.
try {
const wd = sim.worldData, V = wd.__vf
if (!V) { throw 0 }
if (!V.__fm) V.__fm = { ring: new Array(180).fill(0.016), i: 0, since: 0 }
const M = V.__fm
M.ring[M.i] = Math.min(dt, 0.2); M.i = (M.i + 1) % M.ring.length
M.since += dt
if (M.since >= 2.0) {
M.since = 0
const s = M.ring.slice().sort((a, b) => a - b)
const n = s.length
const pct = p => s[Math.min(n - 1, Math.floor(p * n))]
const ms = x => Math.round(x * 10000) / 10
const px = V.px || 0, pz = V.pz || 0
const room = pz < -95 ? 'arena' : pz < -56 ? 'risen-nave' : pz < -8.8 ? 'warren' :
pz > 35 ? 'void' : (px > 18 ? 'woven' : (px > 5 && Math.abs(pz) < 4 ? 'side/time' : 'nave'))
wd.__frameMeter = {
room, pos: [Math.round(px), Math.round(pz)],
med_ms: ms(pct(0.5)), p95_ms: ms(pct(0.95)), max_ms: ms(s[n - 1]),
fps_med: Math.round(1 / Math.max(pct(0.5), 0.001)),
spikes_40: s.filter(x => x > 0.025).length // frames slower than 40fps in the window
}
}
} catch (e) {}
hook · vf-audio
by claude-code
;(() => {
// vf-audio — AUDIO — dread score + reactive music_mod + one-shot SFX (bolt, key, door, heartbeat, shriek, dragon-room swap) (u12)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'da0c8084d2/' + globalThis.__VF_GEO_REV
if (__C['vf-audio@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// audio (node: audio) — VEILFIRE horror soundscape. Composed LAST in the step hook,
// so every whiteboard row it reads is this frame's value:
// uni6 HP · uni15 muzzle · uni16 hitFlash · uni20 orb charge · uni21 orb strike
// flash · uni43 warren state (+2 = THE LAIR) · uni44 keyHeld · uni45 doorOpen ·
// uni47 ambushAlert.
//
// THREE LAYERS, all via sim.worldData (the host consumes + clears them each frame):
// 1. __play_music — a slow, dark, sparse dread SCORE. Set exactly ONCE (V.au_musicStarted
// latch). Loops. Composed as data — the engine synthesizes it live.
// 2. music_mod {brightness,gain} — set EVERY FRAME (continuous). brightness CLOSES a
// master lowpass toward dark as MENACE rises (orb charging/striking, a demon near,
// an ambush burst, or THE LAIR — permanently oppressive) and opens when safe. The
// host glides it; we also low-pass our own target so it never jitters frame to frame.
// 3. __play_sound — one-shots fired on EDGES, each edge-guarded to fire EXACTLY once:
// bolt fire, orb WHIPCRACK, ambusher SHRIEK, player HIT (+ a brief music duck),
// KEY pickup (a scheduled rising arp), DOOR unlock (stone grind), and a low-HP
// two-thump HEARTBEAT on a dt-accumulated cadence that quickens as HP → 0.
//
// Composed with 9 siblings in ONE shared scope: every top-level name is au_-prefixed and
// every scratch field on __vf is au_* — EXCEPT V.dread, which the HUD vignette reads (kept
// + improved). Never throws, never writes NaN, clamps dt. Only ONE __play_sound survives a
// frame, so a priority pick keeps the most dramatic event when two coincide.
const au_fin = (v, d) => (Number.isFinite(v) ? v : (d || 0))
const au_clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v)
const au_clamp01 = (v) => (Number.isFinite(v) ? (v < 0 ? 0 : v > 1 ? 1 : v) : 0)
const au_lerp = (a, b, t) => a + (b - a) * t
// The dread SCORE — slow, dark, sparse. ~7.5s loop at bpm 64 (32 sixteenth-steps).
// A tolling low fifth over a dissonant held cluster, a cold distant bell, and a slow
// heart the building keeps. Not busy — horror is space, not noise.
const AU_SCORE = {
bpm: 64, loop: true, gain: 0.55, swing: 0.0,
tracks: [
// low drone bass — the floor of the dread; a slow tolling fifth C1 → G1
{ inst: 'triangle', gain: 0.6, cutoff: 190, a: 0.8, d: 3.4,
notes: 'C1 . . . . . . . . . . . . . . . G1 . . . . . . . . . . . . . . .' },
// sub reinforcement (sine) — body beneath the drone
{ inst: 'sine', gain: 0.4, cutoff: 140, a: 1.0, d: 3.6,
notes: 'C1 . . . . . . . . . . . . . . . C1 . . . . . . . . . . . . . . .' },
// dissonant pad — a held minor-second / tritone cluster, very low in the mix
{ inst: 'sawtooth', gain: 0.09, cutoff: 480, a: 1.4, d: 2.8,
notes: 'C3+C#3 . . . . . . . . . . . . . . . F#2+C3 . . . . . . . . . . . . . . .' },
// distant bell — sparse, cold, high; the room's far echo
{ inst: 'triangle', gain: 0.13, cutoff: 3200, a: 0.004, d: 1.9,
notes: 'A5 . . . . . . . . . . . . . . . . . . . . . . . . D5 . . . . . . .' },
// a slow distant thud — a heart the building keeps
{ inst: 'kick', gain: 0.22,
notes: 'x . . . . . . . . . . . . . . . x . . . . . . . . . . . . . . .' },
],
}
const DRAGON_SCORE = {
bpm: 96, loop: true, gain: 0.62, swing: 0.0,
tracks: [
{ inst: 'triangle', gain: 0.6, cutoff: 260, a: 0.01, d: 0.4,
notes: 'C1 . C1 . C1 . G1 . C1 . C1 . Eb1 . D1 . C1 . C1 . C1 . G1 . Ab1 . . . G1 . F1 .' },
{ inst: 'sine', gain: 0.45, cutoff: 150, a: 0.02, d: 0.6,
notes: 'C1 . . . . . . . G1 . . . . . . . Ab1 . . . . . . . G1 . . . F1 . . .' },
{ inst: 'sawtooth', gain: 0.16, cutoff: 700, a: 0.01, d: 0.5,
notes: 'C3+F#3 . . . . . . . . . . . Eb3+A3 . . . C3+F#3 . . . . . . . G3+C#4 . . . . . . .' },
{ inst: 'triangle', gain: 0.12, cutoff: 3600, a: 0.003, d: 0.6,
notes: 'C6 . . . G5 . . . C6 . . . Ab5 . . . C6 . . . G5 . . . Eb6 . . . D6 . . .' },
{ inst: 'kick', gain: 0.3,
notes: 'x . . . x . . . x . . . x . . . x . . . x . . . x . . . x . x .' },
],
};
function audio(sim, dt) {
try {
const wd = sim && sim.worldData
if (!wd || typeof wd !== 'object') return
if (!wd.__vf || typeof wd.__vf !== 'object') wd.__vf = {}
const V = wd.__vf
const step = au_clamp(au_fin(dt, 0), 0, 1 / 30)
const u = Array.isArray(wd.gpuUniforms) ? wd.gpuUniforms : null
const uni = (i) => (u && u.length > i ? au_fin(u[i], 0) : 0)
// ── ONE-SHOT MUSIC: compose the dread bed exactly once (survives restore) ────
if (!V.au_musicStarted) {
wd.__play_music = { score: AU_SCORE }
V.au_musicStarted = 1
}
// ── this frame's state (uniforms are authoritative — published + stable) ─────
const px = au_fin(V.px, uni(1)), pz = au_fin(V.pz, uni(3))
const hp = au_clamp01(u ? uni(6) : au_fin(V.hp, 1))
const orbCharge = au_clamp01(uni(20))
const orbStrike = au_clamp01(uni(21))
const warp = uni(43)
const ambushAlert = au_clamp01(uni(47))
// NEW MUSIC entering the dragon room (Galen) — swap the score on entry, revert on leave
const inDragonRoom = warp > 1.5 && pz < -95 && pz > -116 && !!(V.dragon && !V.dragon.dead)
if (inDragonRoom && !V.au_inDragon) { wd.__play_music = { score: DRAGON_SCORE }; V.au_inDragon = 1 }
else if (!inDragonRoom && V.au_inDragon) { wd.__play_music = { score: AU_SCORE }; V.au_inDragon = 0 }
const muzzle = au_fin(u ? uni(15) : V.muzzle, 0)
const hitFlash = au_fin(u ? uni(16) : V.hitFlash, 0)
const keyHeld = uni(44)
const doorOpen = uni(45)
const inLair = warp > 1.5
const lairPenalty = inLair ? 0.5 : 0
const lowHp = au_clamp01((0.5 - hp) / 0.5)
// nearest live demon → closeness [0..1] + max attack wind-up
let nearDemon = 0, demonAtk = 0
if (Array.isArray(V.en)) {
for (const e of V.en) {
if (!e || au_fin(e.hp, 0) <= 0) continue
const d = Math.hypot(px - au_fin(e.x, 0), pz - au_fin(e.z, 0))
if (d < 6) nearDemon = Math.max(nearDemon, (6 - d) / 6)
const at = au_clamp01(e.atk)
if (at > demonAtk) demonAtk = at
}
}
// ── ONE __play_sound survives the frame: a priority picker keeps the loudest ─
let snd = null, pr = -1
const emit = (p, s) => { if (p > pr) { pr = p; snd = s } }
// scheduled notes (key arp / heartbeat 2nd thump) — advance + fire when due.
// Processed first so notes queued THIS frame fire from next frame onward.
if (Array.isArray(V.au_sched) && V.au_sched.length) {
const keep = []
for (const it of V.au_sched) {
it.t -= step
if (it.t <= 0) emit(1, it.s)
else keep.push(it)
}
V.au_sched = keep
}
// 1) BOLT FIRE — muzzle re-spikes to 1.0 on every shot; detect the jump UP so
// rapid fire still triggers even though muzzle never fully decays between shots.
if (muzzle > au_fin(V.au_muzPrev, 0) + 0.25) emit(3, [
{ frequency: 720, duration: 0.05, volume: 0.22, type: 'square' },
{ frequency: 430, duration: 0.07, volume: 0.18, type: 'square' },
])
V.au_muzPrev = muzzle
// 2) KEY PICKUP — uni44 0→1 latch: a cold rising arpeggio (scheduled shimmer)
if (au_fin(V.au_keyPrev, 0) < 0.5 && keyHeld >= 0.5) {
if (!Array.isArray(V.au_sched)) V.au_sched = []
V.au_sched.push({ t: 0.00, s: { frequency: 880, duration: 0.5, volume: 0.16, type: 'triangle' } })
V.au_sched.push({ t: 0.10, s: { frequency: 1318, duration: 0.5, volume: 0.15, type: 'triangle' } })
V.au_sched.push({ t: 0.20, s: { frequency: 1760, duration: 0.7, volume: 0.16, type: 'sine' } })
V.au_sched.push({ t: 0.32, s: { frequency: 2637, duration: 0.6, volume: 0.10, type: 'sine' } })
}
V.au_keyPrev = keyHeld
// 3) DOOR UNLOCK — uni45 crossing 0.5: a deep stone-grind rumble
if (au_fin(V.au_doorPrev, 0) < 0.5 && doorOpen >= 0.5) emit(4, [
{ frequency: 46, duration: 0.9, volume: 0.34, type: 'sawtooth' },
{ frequency: 61, duration: 0.8, volume: 0.24, type: 'sawtooth' },
{ frequency: 30, duration: 1.0, volume: 0.30, type: 'sine' },
])
V.au_doorPrev = doorOpen
// 4) LOW-HP HEARTBEAT — two thumps on a dt-accumulated cadence that quickens as
// HP → 0; silent (and reset) once HP recovers past the threshold.
// only while ALIVE and low — a dead/resetting player has no heartbeat, and any
// scheduled 2nd-thumps are PURGED so the beat can't survive the respawn (Galen).
const alive = !(au_fin(V.dead, 0) > 0) && (V.game !== 1) && hp > 0.02
if (hp < 0.35 && alive) {
const period = au_lerp(0.5, 1.15, hp / 0.35) // faster as hp → 0
V.au_hbClock = au_fin(V.au_hbClock, 0) + step
if (V.au_hbClock >= period) {
V.au_hbClock = 0
emit(5, { frequency: 52, duration: 0.13, volume: 0.40, type: 'sine' })
if (!Array.isArray(V.au_sched)) V.au_sched = []
V.au_sched.push({ t: 0.17, hb: true, s: { frequency: 44, duration: 0.15, volume: 0.32, type: 'sine' } }) // 2nd thump (tagged)
}
} else {
V.au_hbClock = 0
if (Array.isArray(V.au_sched)) V.au_sched = V.au_sched.filter(x => !x.hb) // purge pending thumps
}
// 5) PLAYER HIT — hitFlash re-spikes on every strike: jump-up → low thud + duck
if (hitFlash > au_fin(V.au_hitPrev, 0) + 0.3) {
emit(6, [
{ frequency: 58, duration: 0.22, volume: 0.42, type: 'sine' },
{ frequency: 96, duration: 0.14, volume: 0.20, type: 'triangle' },
])
V.au_duck = 0.28
}
V.au_hitPrev = hitFlash
V.au_duck = Math.max(0, au_fin(V.au_duck, 0) - step)
// 6) AMBUSHER BURST — uni47 rising past 0.5: a detuned high shriek
if (au_fin(V.au_ambPrev, 0) < 0.5 && ambushAlert >= 0.5) emit(7, [
{ frequency: 1240, duration: 0.28, volume: 0.26, type: 'sawtooth' },
{ frequency: 1310, duration: 0.28, volume: 0.22, type: 'sawtooth' },
{ frequency: 1970, duration: 0.20, volume: 0.16, type: 'square' },
{ frequency: 623, duration: 0.32, volume: 0.20, type: 'sawtooth' },
])
V.au_ambPrev = ambushAlert
// 7) ORB WHIPCRACK — uni21 rising past 0.5: a low detuned CRACK (saw body + click)
if (au_fin(V.au_crackPrev, 0) < 0.5 && orbStrike >= 0.5) emit(8, [
{ frequency: 68, duration: 0.20, volume: 0.42, type: 'sawtooth' },
{ frequency: 91, duration: 0.16, volume: 0.30, type: 'sawtooth' },
{ frequency: 210, duration: 0.05, volume: 0.24, type: 'square' },
{ frequency: 1500, duration: 0.03, volume: 0.14, type: 'square' },
])
V.au_crackPrev = orbStrike
if (snd != null) wd.__play_sound = snd
// ── REACTIVE music_mod — brightness CLOSES with menace, opens when safe ──────
const brightTarget = au_clamp(
0.85 - 0.5 * orbCharge - 0.45 * orbStrike - 0.6 * ambushAlert - lairPenalty - 0.4 * nearDemon - 0.15 * lowHp,
0.06, 0.9)
const menace = au_clamp01(
0.7 * orbCharge + 0.6 * orbStrike + 0.9 * ambushAlert + lairPenalty + 0.6 * nearDemon + 0.4 * demonAtk + 0.3 * lowHp)
let gainTarget = au_clamp(0.62 + 0.3 * menace, 0.5, 0.98)
const duckFrac = au_clamp01(au_fin(V.au_duck, 0) / 0.28)
gainTarget *= (1 - 0.45 * duckFrac) // brief dip when the player is struck
// our own low-pass so the target never jitters frame-to-frame (the host glides too)
const k = au_clamp01(step * 4)
V.au_bright = au_lerp(au_fin(V.au_bright, brightTarget), brightTarget, k)
V.au_gain = au_lerp(au_fin(V.au_gain, gainTarget), gainTarget, k)
const brightness = au_clamp(au_fin(V.au_bright, 0.6), 0.06, 0.95)
const gain = au_clamp(au_fin(V.au_gain, 0.7), 0.0, 1.0)
wd.music_mod = { brightness, gain }
// ── HUD dread (row 12) — near-demon basis (as the stub) + orb / ambush / lair ─
let near = 0
if (Array.isArray(V.en)) for (const e of V.en) {
if (e && au_fin(e.hp, 0) > 0 && Math.hypot(px - au_fin(e.x, 0), pz - au_fin(e.z, 0)) < 4.0) near++
}
V.dread = au_clamp01(near * 0.3 + 0.35 * orbCharge + 0.5 * ambushAlert + (inLair ? 0.4 : 0))
if (u && u.length > 12) u[12] = V.dread
} catch (e) { /* composed with 9 siblings — never throw */ }
}
__C['vf-audio'] = audio
__C['vf-audio@rev'] = __REV
}
try { __C['vf-audio'](sim, dt) } catch (e) {}
})()hook · vf-arena-dragon
by Claude (Fable · E)
;(() => {
// vf-arena-dragon — THE OCTAGON ARENA — PENTARCH the dragon v2: PROWLS the arena
// (accel/decel strafe arcs, stride bob), LUNGE-BITES at close range, FLIGHT phase
// (takeoff at half HP or periodically: wing-beat bob, circle-strafe, lead-aimed dive
// shots, landing settle), FLINCH recoil on bolt hits, crash-then-sink death.
// Volleys, hp-12 killable law, fireball/burst damage, trails: all preserved.
// Geometry via the worker-global __VF_GEO; cached build-once in __VF_FNS.
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'dragon-v3.1-magmapour/' + globalThis.__VF_GEO_REV
if (__C['vf-arena-dragon@rev'] !== __REV) {
const { mazeBlocked } = globalThis.__VF_GEO
const AC = { x: 4.012, z: -106.0 } // octagon arena center (apothem 9)
function dragon(sim, dt) {
try {
const wd = sim.worldData; const V = wd.__vf; if (!V || !Array.isArray(V.pop)) return;
const u = Array.isArray(wd.gpuUniforms) ? wd.gpuUniforms : null;
const cl = (x) => Math.max(0, Math.min(1, x));
// RE-ENTRY AFTER A KILL: the arena rebuilds its guardian — a fresh dragon
// rises in the smoke-flash. The crystal never drops twice (V.crystal guard).
if (V.dragon && V.dragon.dead && V.dragon.sunk && V.dragon.present !== 1 && (V.pz != null ? V.pz : -6) < -96) V.dragon = null
const D = V.dragon || (V.dragon = { x: 4.012, y: 0.0, z: -106.0, yaw: 0, charge: 0, fireT: 0, hp: 12, fb: [], bursts: [], fired: -1, hpCv: true });
// heal OLD saves (pre-killable hp:1 marker) into the killable law — ONCE.
// The original healed on every hp===1 tick, making 1 HP an accidental
// full-heal (only same-tick multi-hits could ever kill it).
if (D.hp == null) { D.hp = 12; D.hpCv = true }
else if (D.hp === 1 && !D.hpCv) { D.hp = 12; D.hpCv = true }
// v2 state (old saves heal in)
if (D.st == null) { D.st = 'prowl'; D.stT = 0; D.tx = D.x; D.tz = D.z; D.vx = 0; D.vz = 0; D.strideP = 0; D.air = 0; D.beatP = 0; D.flinch = 0; D.lunge = 0; D.flyAt = 14; D.diveT = 0; D.ppx = null; D.ppz = null; D.bounced = false; D.phase = 0 }
const px = V.px != null ? V.px : 0, py = V.py != null ? V.py : 1.7, pz = V.pz != null ? V.pz : -6;
// player velocity (for lead aim)
const pvx = D.ppx == null ? 0 : (px - D.ppx) / Math.max(dt, 1e-4), pvz = D.ppz == null ? 0 : (pz - D.ppz) / Math.max(dt, 1e-4)
D.ppx = px; D.ppz = pz
const distToPlayer = Math.hypot(px - D.x, pz - D.z);
const engaged = D.present === 1 && distToPlayer < 46 && !D.dead;
// NEAR gate — active locomotion/flight only when the player is actually at
// the arena. From the column room (25-46u) it holds its post: breathing,
// mostly wall-occluded, cheap. Also restores the loom-at-the-end reveal.
const near = distToPlayer < 24;
// ── REAL SMOKE — billowing particles with their own drift, not orb rings.
// Bursts on spawn/despawn/glitch-out; advances every tick regardless of
// the dragon's presence so the cloud outlives the body. ──
if (!Array.isArray(V.vfSmoke)) V.vfSmoke = []
const smokeBurst = (sx, sz, n, big) => {
for (let s9 = 0; s9 < n; s9++) {
const a9 = s9 * 2.399963, r9 = 0.25 + (s9 % 6) * 0.28 * (big ? 1.6 : 1)
V.vfSmoke.push({ x: sx + Math.cos(a9) * r9, y: 0.25 + (s9 % 4) * 0.55, z: sz + Math.sin(a9) * r9,
vx: Math.cos(a9) * (0.5 + (s9 % 3) * 0.35), vy: 0.9 + (s9 % 5) * 0.28, vz: Math.sin(a9) * (0.5 + (s9 % 3) * 0.35),
age: -(s9 % 8) * 0.03, life: (big ? 1.9 : 1.35) })
}
}
{
const keepS = []
for (const sp of V.vfSmoke) {
sp.age += dt
if (sp.age < 0) { keepS.push(sp); continue }
if (sp.age >= sp.life) continue
sp.vx *= (1 - dt * 1.1); sp.vz *= (1 - dt * 1.1); sp.vy *= (1 - dt * 0.35)
sp.x += sp.vx * dt; sp.y += sp.vy * dt; sp.z += sp.vz * dt
const f9 = 1 - sp.age / sp.life
V.pop.push(sp.x, sp.y, sp.z, 5, f9 * 0.85, 0, 0, 0)
if (sp.age < 0.12) V.pop.push(sp.x, sp.y * 0.6 + 0.5, sp.z, 3, 1, 0, 0, 0) // white flash core
keepS.push(sp)
}
V.vfSmoke = keepS
}
// ── THE GLITCH FIGHT (Galen): weapons cannot hurt the projection — the
// THRESHOLD can. Despawn it MID-CHARGE (cross the gable while it winds
// up) and the re-render comes back corrupted. Three destabilizations
// and it tears out of existence, leaving the crystal shard. ──
if (V.dragonGlitch == null) V.dragonGlitch = 0
V.dragonGlitchT = Math.max(0, (V.dragonGlitchT || 0) - dt)
const inArena = pz < -96
if (D.present == null) D.present = 0
if (V.dragonSlain) {
D.present = 0 // the arena is freed
} else if (inArena && D.present === 0 && !D.dead) {
D.present = 1
smokeBurst(D.x, D.z, 30, false)
wd.__play_sound = [{ frequency: 70, duration: 0.5, volume: 0.32, type: 'triangle' }, { frequency: 620, duration: 0.07, volume: 0.22, type: 'square' }]
if (V.dragonGlitch >= 4) { D.st = 'glitchout'; D.stT = 0; V.dragonGlitchT = 2.0 }
else if (V.dragonGlitch > 0) V.dragonGlitchT = Math.max(V.dragonGlitchT, 0.7) // it returns... wrong
} else if (!inArena && D.present === 1) {
D.present = 0
smokeBurst(D.x, D.z, 18, false)
wd.__play_sound = [{ frequency: 880, duration: 0.08, volume: 0.22, type: 'square' }, { frequency: 110, duration: 0.32, volume: 0.26, type: 'sine' }]
if (D.charge > 0.45 && !D.dead) { // caught MID-CHARGE → destabilized
V.dragonGlitch = Math.min(4, V.dragonGlitch + 1) // FOUR threshold catches to fully un-make it (Galen)
V.dragonGlitchT = 1.2
wd.__play_sound = [{ frequency: 1400, duration: 0.06, volume: 0.26, type: 'square' }, { frequency: 47, duration: 0.4, volume: 0.3, type: 'sawtooth' }]
}
D.fb = []; D.bursts = []
}
// GLITCH NOISES — the corruption chatters: short digital chirps at random
// intervals, denser and harsher per destabilization; frantic during the tear.
if (D.present === 1 && (V.dragonGlitch > 0 || (V.dragonGlitchT || 0) > 0.05)) {
D.gNoiseT = (D.gNoiseT == null ? 0.3 : D.gNoiseT) - dt
if (D.gNoiseT <= 0) {
const gr = Math.abs(Math.sin(V.t * 613.7))
const lvl = V.dragonGlitch
D.gNoiseT = (D.st === 'glitchout' ? 0.09 : 0.35 + gr * 1.0 / (1 + lvl))
const snd = [{ frequency: 700 + gr * 1900, duration: 0.02 + gr * 0.04, volume: 0.09 + lvl * 0.07, type: 'square' }]
if (gr > 0.68) snd.push({ frequency: 52 + gr * 30, duration: 0.09, volume: 0.14 + lvl * 0.05, type: 'sawtooth' })
wd.__play_sound = snd
}
}
// GLITCH-OUT — the projection tears apart in front of the player
if (D.st === 'glitchout' && D.present === 1) {
V.dragonGlitchT = Math.max(V.dragonGlitchT, 0.5)
if (D.stT > 1.4) {
V.dragonSlain = true; D.present = 0
smokeBurst(D.x, D.z, 44, true)
if (!V.crystal) V.crystal = { x: D.x, z: D.z, taken: false }
wd.__play_sound = [{ frequency: 1800, duration: 0.09, volume: 0.28, type: 'square' }, { frequency: 240, duration: 0.5, volume: 0.3, type: 'sawtooth' }, { frequency: 36, duration: 1.1, volume: 0.32, type: 'triangle' }]
}
}
D.stT += dt
D.flinch = Math.max(0, D.flinch - dt * 2.6)
D.lunge = Math.max(0, D.lunge - dt * 1.8)
// ── LOCOMOTION ──────────────────────────────────────────────────────
const spdMax = D.st === 'fly' ? 5.2 : 3.0
if (!D.dead && engaged && D.st !== 'glitchout') {
if (D.st === 'prowl') {
if (!near) { D.tx = 4.012; D.tz = -110.5 } // player far → walk home, hold the post
// pick strafe targets on an arc around the arena center, biased to face the player
else if (D.stT > 2.8 || Math.hypot(D.tx - D.x, D.tz - D.z) < 0.7) {
D.stT = 0
const bear = Math.atan2(px - AC.x, pz - AC.z) // player bearing from center
const a = bear + Math.PI + (Math.sin(V.t * 1.7) * 1.1) // opposite side ± swing
const r = 3.2 + Math.abs(Math.sin(V.t * 0.9)) * 3.0
D.tx = AC.x + Math.sin(a) * r; D.tz = AC.z + Math.cos(a) * r
}
// LUNGE when the player closes in (and it's off cooldown)
if (distToPlayer < 6.2 && D.lunge <= 0 && D.stT > 0.8) {
D.st = 'lungeing'; D.stT = 0; D.lunge = 1; D.bit = false // re-arm the bite (the old reset was dead code — stT was zeroed before it could run)
D.tx = px - Math.sin(D.yaw) * 1.4; D.tz = pz - Math.cos(D.yaw) * 1.4
}
// FLIGHT trigger: half HP once, or periodically — only with the player NEAR
if (near) D.flyAt -= dt
if (near && ((D.hp <= 6 && !D.flewLow) || D.flyAt <= 0)) { D.flewLow = D.flewLow || D.hp <= 6; D.st = 'takeoff'; D.stT = 0; D.flyAt = 16 + Math.abs(Math.sin(V.t)) * 8 }
} else if (D.st === 'lungeing') {
if (D.stT > 0.85) { D.st = 'prowl'; D.stT = 0 }
// BITE: close + mid-lunge = damage once
if (!D.bit && D.stT > 0.2 && Math.hypot(px - D.x, pz - D.z) < 2.7) {
D.bit = true; V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.22); V.hitFlash = 0.9; V.shake = 0.7
wd.__play_sound = [{ frequency: 130, duration: 0.18, volume: 0.25, type: 'sawtooth' }]
}
if (D.stT > 0.85) D.bit = false
} else if (D.st === 'takeoff') {
D.air = Math.min(1, D.air + dt / 1.1)
if (D.air >= 1) { D.st = 'fly'; D.stT = 0; D.diveT = 0 }
D.tx = D.x; D.tz = D.z
} else if (D.st === 'fly') {
if (!near) { D.st = 'land'; D.stT = 0 } // player left → come down
// circle-strafe: orbit the center, drifting around the player's bearing
const bear = Math.atan2(D.x - AC.x, D.z - AC.z)
const a = bear + dt * 0.55 // orbit angular speed
const r = 5.6
D.tx = AC.x + Math.sin(a + 0.5) * r; D.tz = AC.z + Math.cos(a + 0.5) * r
if (D.stT > 9) { D.st = 'land'; D.stT = 0 }
} else if (D.st === 'land') {
D.air = Math.max(0, D.air - dt / 1.3)
if (D.air <= 0) { D.st = 'prowl'; D.stT = 0 }
}
// steer toward target with accel/decel (weight)
const dx = D.tx - D.x, dz = D.tz - D.z, dd = Math.hypot(dx, dz)
const want = dd > 0.05 ? Math.min(spdMax, dd * 2.2) : 0
const wvx = dd > 0.05 ? dx / dd * want : 0, wvz = dd > 0.05 ? dz / dd * want : 0
const ACC = D.st === 'fly' ? 4.5 : 6.0
D.vx += (wvx - D.vx) * Math.min(1, dt * ACC / Math.max(spdMax, 1))
D.vz += (wvz - D.vz) * Math.min(1, dt * ACC / Math.max(spdMax, 1))
// flinch recoil pushes away from the player
if (D.flinch > 0) { const fb = Math.atan2(D.x - px, D.z - pz); D.vx += Math.sin(fb) * D.flinch * 2.2 * dt * 8; D.vz += Math.cos(fb) * D.flinch * 2.2 * dt * 8 }
D.x += D.vx * dt; D.z += D.vz * dt
// keep inside the octagon (2.4u wall margin) and off the entry tunnel
const rx = D.x - AC.x, rz = D.z - AC.z, rr = Math.hypot(rx, rz)
if (rr > 6.6) { D.x = AC.x + rx / rr * 6.6; D.z = AC.z + rz / rr * 6.6 }
if (D.z > -100.5) D.z = -100.5
// never stand IN the player
const pd = Math.hypot(px - D.x, pz - D.z)
if (pd < 2.0 && pd > 1e-3) { D.x = px + (D.x - px) / pd * 2.0; D.z = pz + (D.z - pz) / pd * 2.0 }
// vertical: stride bob grounded · wing-beat bob aloft
const spd = Math.hypot(D.vx, D.vz)
D.strideP += dt * spd * 3.2 * (1 - D.air)
D.beatP += dt * (D.air > 0 ? 6.5 : 0)
const groundY = Math.abs(Math.sin(D.strideP)) * 0.10 * Math.min(1, spd / 1.5)
const flyY = 3.6 + Math.sin(D.beatP) * 0.28
D.y = groundY * (1 - D.air) + flyY * D.air
} else if (!D.dead) {
D.vx = 0; D.vz = 0; D.charge = 0
D.air = Math.max(0, D.air - dt / 1.3)
D.y = D.air > 0 ? 3.6 * D.air : 0
}
// yaw: face the player (grounded / attacking), lean into motion when flying fast
const spdN = Math.hypot(D.vx, D.vz)
const faceP = Math.atan2(px - D.x, pz - D.z)
const faceM = spdN > 0.4 ? Math.atan2(D.vx, D.vz) : faceP
const blend = D.st === 'fly' ? Math.min(1, spdN / 4) * 0.65 : 0
let wantYaw = faceP
if (blend > 0) { let dm = faceM - faceP; while (dm > Math.PI) dm -= 2 * Math.PI; while (dm < -Math.PI) dm += 2 * Math.PI; wantYaw = faceP + dm * blend }
let dyaw = wantYaw - D.yaw; while (dyaw > Math.PI) dyaw -= 2 * Math.PI; while (dyaw < -Math.PI) dyaw += 2 * Math.PI;
D.yaw += dyaw * Math.min(1, dt * (D.st === 'lungeing' ? 4.5 : 2.6));
// ── FIRE ────────────────────────────────────────────────────────────
if (engaged && D.st !== 'takeoff' && D.st !== 'land' && D.st !== 'glitchout') {
if (D.air < 0.5) {
// grounded POUR (Galen): the head lowers with the charge (rig reads u51)
// and the fire POURS from the maw like magma — a stream of droplets that
// splash into burning pools on the arena floor.
D.fireT += dt;
const WIND = 2.6, POUR = 1.1, PERIOD = WIND + POUR + 0.9;
const ph = D.fireT % PERIOD;
D.charge = ph < WIND ? (ph / WIND) : (ph < WIND + POUR ? 1 : Math.max(0, 1 - (ph - WIND - POUR) * 3));
if (ph >= WIND && ph < WIND + POUR) {
D.dropT = (D.dropT || 0) - dt
if (D.dropT <= 0) {
D.dropT = 0.055
const rr = Math.abs(Math.sin(V.t * 351.3))
// the maw is LOW now (head dropped ~1.45u at full charge)
const jx = D.x + Math.sin(D.yaw) * 1.6, jz = D.z + Math.cos(D.yaw) * 1.6, jy = D.y + 2.4 - 1.3
D.fb.push({ x: jx, y: jy, z: jz,
vx: Math.sin(D.yaw + (rr - 0.5) * 0.3) * (2.0 + rr * 1.8), vy: -0.4,
vz: Math.cos(D.yaw + (rr - 0.5) * 0.3) * (2.0 + rr * 1.8), life: 0.8, tr: [], pour: 1 });
}
}
} else {
// AIRBORNE: single lead-aimed dive shots on a faster cadence
D.charge = Math.max(0, D.charge - dt * 2)
D.diveT += dt
let adyaw = faceP - D.yaw; while (adyaw > Math.PI) adyaw -= 2 * Math.PI; while (adyaw < -Math.PI) adyaw += 2 * Math.PI
if (D.diveT > 1.35 && Math.abs(adyaw) < 0.6) {
D.diveT = 0; D.charge = 1
const lx = px + pvx * 0.35, lz = pz + pvz * 0.35 // lead the runner
const ly = Math.atan2(lx - D.x, lz - D.z)
const jx = D.x + Math.sin(ly) * 1.2, jz = D.z + Math.cos(ly) * 1.2, jy = D.y + 1.6
const SPD = 13.5
D.fb.push({ x: jx, y: jy, z: jz, vx: Math.sin(ly) * SPD, vy: 0.6, vz: Math.cos(ly) * SPD, life: 1, tr: [] })
}
}
} else if (!engaged) { D.charge = 0 }
// ── WEAPONS DO NOT HURT THE PROJECTION (Galen). hp pinned; the only
// wound channel is the threshold glitch above. (Weapons are warped
// away inside the pixel dimension anyway — vf-weapon-bolts strips them.)
if (!D.dead) D.hp = 12
// death: CRASH (fall, one bounce, slow spin) then sink
if (D.dead) {
D.charge = 0
if (!D.sunk) {
D.crashV = (D.crashV || 0) - 9.5 * dt
D.y += D.crashV * dt
D.yaw += dt * 0.9
if (D.y <= 0.1 && !D.bounced) { D.bounced = true; D.crashV = Math.abs(D.crashV) * 0.28; D.y = 0.1; V.shake = Math.max(V.shake || 0, 0.5) }
else if (D.y <= 0.05 && D.bounced) { D.sunkT = (D.sunkT || 0) + dt; if (D.sunkT > 1.4) D.sunk = true }
} else {
D.y = Math.max(-7, D.y - dt * 0.9)
// the carcass yields THE UPGRADED CRYSTAL at the crash site (once)
if (!V.crystal && D.y < -2) V.crystal = { x: D.crashX != null ? D.crashX : D.x, z: D.crashZ != null ? D.crashZ : D.z, taken: false }
}
}
// ── THE UPGRADED CRYSTAL — pulsing gem where the dragon fell; walk over it
// to take it (V.hasCrystal). It is the key that makes the MANIFOLD orb
// in the nave killable (vf-nave-orb reads the flag). ──
const C = V.crystal
if (C && !C.taken) {
// GLITCH SHARD — six white points tracing a slowly turning diamond that
// flickers like a rendering artifact (no fire-ball pickups in this room)
const cyc = (V.t || 0) * 1.3
if (Math.sin(V.t * 31.0) > -0.85) {
for (let a = 0; a < 6; a++) {
const th = cyc + a * 1.0472
const rr = 0.28 + 0.06 * Math.sin(V.t * 2.1 + a)
V.pop.push(C.x + Math.cos(th) * rr, 1.0 + Math.sin(a * 2.0944 + cyc) * 0.3, C.z + Math.sin(th) * rr, 3, 1, 0, 0, 0)
}
}
if (Math.hypot(px - C.x, pz - C.z) < 1.7) {
C.taken = true; V.hasCrystal = true
wd.__play_sound = [{ frequency: 660, duration: 0.12, volume: 0.22, type: 'sine' }, { frequency: 880, duration: 0.14, volume: 0.2, type: 'sine' }, { frequency: 1320, duration: 0.2, volume: 0.18, type: 'triangle' }]
}
}
// ── FIREBALLS / BURSTS (unchanged laws) ─────────────────────────────
for (const b9 of D.fb) {
if (Math.hypot(b9.x - px, b9.z - pz) < 0.7 && b9.y < 2.4) { b9.life = 0; D.bursts.push({ x: b9.x, y: Math.max(0.25, b9.y), z: b9.z, age: 0, life: 0.45 }); V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.16); V.hitFlash = 0.9; V.shake = 0.6 }
}
for (const e9 of D.bursts) {
if (e9.age < 0 || e9.age >= e9.life) continue
const rad9 = 0.3 + (e9.age / e9.life) * 1.7
if (Math.hypot(e9.x - px, e9.z - pz) < rad9 + 0.55 && e9.y < 3.0) { V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.5 * dt); V.hitFlash = Math.max(V.hitFlash || 0, 0.5) }
}
// MAGMA POOLS — where the pour lands, the floor burns for seconds
if (!Array.isArray(D.pools)) D.pools = []
{
const pkeep = []
for (const P9 of D.pools) {
P9.age += dt
if (P9.age >= P9.life) continue
pkeep.push(P9)
const f9 = 1 - P9.age / P9.life
const rad9 = 0.35 + Math.min(1, P9.age * 2.5) * P9.r
V.pop.push(P9.x, 0.12, P9.z, 7, f9 * 0.8, 0, 0, 0)
for (let a9 = 0; a9 < 4; a9++) {
const th9 = a9 * 1.5708 + P9.age * 0.9
V.pop.push(P9.x + Math.cos(th9) * rad9, 0.1, P9.z + Math.sin(th9) * rad9, 7, f9 * 0.55, 0, 0, 0)
}
if (Math.hypot(px - P9.x, pz - P9.z) < rad9 + 0.5 && py < 2.4) {
V.hp = Math.max(0, (V.hp == null ? 1 : V.hp) - 0.35 * dt); V.hitFlash = Math.max(V.hitFlash || 0, 0.4)
}
}
D.pools = pkeep
}
const G = 11, FB_R = 0.22, keep = [];
for (const b of D.fb) {
b.vy -= G * dt;
const nx = b.x + b.vx * dt, ny = b.y + b.vy * dt, nz = b.z + b.vz * dt;
const hitWall = (typeof mazeBlocked === 'function') ? mazeBlocked(nx, nz, FB_R) : false;
const hitFloor = ny <= 0.15;
b.x = nx; b.y = ny; b.z = nz; b.life -= dt / 2.2;
if (hitWall || hitFloor || b.life <= 0) {
if (b.pour && hitFloor && D.pools.length < 12) D.pools.push({ x: b.x, z: b.z, age: 0, life: 3.2, r: 0.7 + Math.abs(Math.sin(b.x * 7 + b.z * 5)) * 0.5 })
D.bursts.push({ x: b.x, y: Math.max(0.25, b.y), z: b.z, age: 0, life: b.pour ? 0.25 : 0.45 });
continue;
}
b.tr.push([b.x, b.y, b.z]); if (b.tr.length > 4) b.tr.shift();
keep.push(b);
V.pop.push(b.x, b.y, b.z, 7, cl(b.life), 0, 0, 0);
for (let ti = 0; ti < b.tr.length - 1; ti++) {
const tp = b.tr[ti];
V.pop.push(tp[0], tp[1], tp[2], 7, cl(b.life) * ((ti + 1) / b.tr.length) * 0.55, 0, 0, 0);
}
}
D.fb = keep;
const bkeep = [];
for (const e of D.bursts) {
e.age += dt;
if (e.age >= e.life) continue;
bkeep.push(e);
const r01 = e.age / e.life, fade = 1 - r01, rad = 0.3 + r01 * 1.7;
if (r01 < 0.4) V.pop.push(e.x, e.y + 0.2, e.z, 7, fade, 0, 0, 0);
for (let a = 0; a < 8; a++) {
const ang = a * 0.7853981633974483;
V.pop.push(e.x + Math.cos(ang) * rad, e.y + 0.2 + Math.sin(r01 * 3.14159) * 0.6, e.z + Math.sin(ang) * rad, 5, fade * 0.8, 0, 0, 0);
}
}
D.bursts = bkeep;
// RIG PHASE — the shader poses the whole body (breath/sway/tail + wing
// flap aloft) from pop slot 5; it was hard-0 since birth = frozen statue.
// Advance faster while moving / flying; a corpse stops breathing.
if (!D.dead) D.phase = (D.phase || 0) + dt * (1.0 + Math.hypot(D.vx, D.vz) * 0.35 + D.air * 1.4)
// jaw gape = charge OR lunge bite (entity slot 7 drives the shader jaw/throat)
const jaw = Math.max(cl(D.charge), D.st === 'lungeing' ? cl(D.lunge) : 0)
if (D.present === 1 && D.y > -6.5) V.pop.push(D.x, D.y, D.z, 10, cl(D.hp / 12), D.phase || 0, D.yaw, jaw);
if (engaged) D.wingGrow = Math.min(1, (D.wingGrow || 0) + dt / 3.5);
const flare = Math.max(cl(D.wingGrow || 0), D.air) // wings mantle fully aloft
// GAIT channels — u59 walk01 (ground speed norm), u60 stride phase,
// u61/u62 unit motion dir in the dragon's LOCAL frame (shader-side feet
// swing along the real travel direction, so strafes read as side-stepping)
const gspd = Math.hypot(D.vx || 0, D.vz || 0)
const walk01 = Math.min(1, gspd / 2.6) * (1 - (D.air || 0))
const gcy = Math.cos(-D.yaw), gsy = Math.sin(-D.yaw)
let lux = 0, luz = 1
if (gspd > 0.15) { const nx0 = D.vx / gspd, nz0 = D.vz / gspd; lux = gcy * nx0 + gsy * nz0; luz = -gsy * nx0 + gcy * nz0 }
if (u) { u[50] = engaged ? 1 : 0.4; u[51] = jaw; u[52] = flare; u[53] = D.x; u[54] = D.y; u[55] = D.z; u[56] = D.yaw; u[57] = cl(D.flinch); u[58] = cl(D.air); u[59] = walk01; u[60] = D.strideP || 0; u[61] = lux; u[62] = luz; u[63] = Math.max(V.dragonGlitch * 0.22, Math.min(1, V.dragonGlitchT || 0)) }
} catch (e) { /* never throw — composed with siblings */ }
}
__C['vf-arena-dragon'] = dragon
__C['vf-arena-dragon@rev'] = __REV
}
try { __C['vf-arena-dragon'](sim, dt) } catch (e) {}
})()
hook · vf-weapon3
by Claude (Fable · E)
// VEILFIRE-3D · WEAPON 3 (plasma) — RETIRED (Galen): plasma removed; slot 3 is the
// VEILFIRE BALL now (the sent-out lantern). No pickup, no fire, no gun-suppress.
// Clears any stale plasma state so it can't re-arm. No-op kept for the registry slot.
try {
const V = sim.worldData.__vf
if (V) { V.hasW3 = 0; V.w3pick = null; V.w3ammo = 0; if (V.weapon === 3 && V.__wasPlasma) V.weapon = 1 }
} catch (e) {}
hook · vf-weapon-bolts
by claude-code
;(() => {
// vf-weapon-bolts — WEAPONS — click-to-fire bolts along look dir, bounce, ammo + muzzle flash (u7,u15)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'b2594c0f19/' + globalThis.__VF_GEO_REV
if (__C['vf-weapon-bolts@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// projectiles (node: projectiles) — fire + travel + expire + WALL BOUNCE. Fragment
// run after enemies. Fires on click/space from the player along yaw; bolts (kind 3)
// fly straight (horizontal only — no vy, so wall planes fully bound them), expire by
// life; pushed to __vf.pop for the renderer + kept in __vf.bolts for combat's
// hit-test. Owns ammo (row 7) + muzzle flash (row 15).
//
// WAVE 3 (Galen): "the players shot should bounce on walls once." Each bolt carries
// bounced=0. Per frame, if the next position would cross a wall, the velocity is
// mirrored across that wall's axis-aligned normal (with a ~0.85 speed loss for feel),
// the bolt is left at its last free (in-air) position, and bounced flips to 1. A bolt
// that has already bounced dies (life=0) the moment it meets ANY surface.
//
// The bolt/wall collision MIRRORS shooter3/hooks/movement.mjs EXACTLY (one truth, two
// callers — we do NOT edit movement, we copy its wall extents). The helpers live
// INSIDE this function on purpose: fragments are concatenated into one shared scope
// at compose time (veilfire-cartridge.mjs), where movement already declares R/walkable
// at top level — function-local copies avoid the redeclare collision while still
// working when this module is imported standalone (the bounce sim). Where these
// extents and the shader disagree the SHADER wins. Cylinders (column/pillars/altar)
// count as surfaces and bounce with an axis-aligned approximation.
function projectiles(sim, dt) {
const R = 0.4
const WCOLZ = -12.5, WCOLR = 0.5
const CORR_WALLZ = -24, CORR_HW = 1.0, CHAMB_HW = 2.5, CHAMB_D = 4.0
function inCorridor(x, z, L) {
const farZ = CORR_WALLZ - L
// BR = a BULLET's radius, not the player's (R=0.4 ate most of the 1u-wide
// tube — off-axis shots in the corridor bounced at the muzzle)
if (z >= farZ) return Math.abs(x) <= CORR_HW - 0.12
if (z >= farZ - CHAMB_D) return Math.abs(x) <= CHAMB_HW - 0.12
return false
}
function blocked(x, z, warp) {
if (z > -8.8) { // nave colonnade
const cr = 0.4 + R
for (const cx of [-3.2, 3.2])
for (const cz of [-8, -4, 0, 4, 8])
if ((x - cx) * (x - cx) + (z - cz) * (z - cz) < cr * cr) return true
return false
}
if (x * x + (z - WCOLZ) * (z - WCOLZ) < (WCOLR + R) * (WCOLR + R)) return true // grand column
if (z < WCOLZ) {
if (warp < 0) { // Room B — the altar-key floats
// (not a bolt collider; the old z=-18 sphere here was stale — shrine is z=-27 now)
} else { // Room A / lair — pillar rows x=±2.4
const f = (z + 1.5) / 3
const pz = ((f - Math.floor(f)) - 0.5) * 3
const pr = 0.35 + R
if ((Math.abs(x) - 2.4) ** 2 + pz * pz < pr * pr) return true
}
}
return false
}
// walkable air for a bolt — identical extents to movement.walkable
function walkable(x, z, warp, L, doorOpen) {
if (z < -24 + R) {
if (warp < 0.5 || !doorOpen) return false
// THE WORLD GREW past this local copy (Galen: "the bullet is bouncing off
// the pixel render layer" — the nave mouth read as a WALL to bolts):
// corridor → RISEN NAVE → exit tunnel → OCTAGON ARENA are all air now.
const risen = z <= -50 && z >= -95.5 && x >= -5.75 + 0.12 && x <= 5.75 - 0.12
const tun = z <= -95 && z >= -100.5 && Math.abs(x - 4.012) <= 1.7 - 0.12
const arena = z < -97 && Math.hypot(x - 4.012, z - (-106)) <= 9 - R
return inCorridor(x, z, L) || risen || tun || arena
}
if (z <= -9.0 + R && z >= -9.8 - R && Math.abs(x) > 2.8 - R) return false // nave -z wall (arch pierced)
const main = x >= -4 + R && x <= 4 - R && z >= -24 + R && z <= 9 - R
const side = x >= 5 + R && x <= 11 - R && z >= -3.5 + R && z <= 3.5 - R
const door = x >= 3.3 && x <= 5.7 && z >= -1.5 + R && z <= 1.5 - R
const hall = (x >= 10.5 && x <= 18 - R && z >= -1.0 + R && z <= 1.0 - R) // PERMANENT HALLWAY (Galen)
const vch = (x >= -4 + R && x <= 4 - R && z >= 36 + R && z <= 44 - R) // VOID CHAMBER (Galen)
// NEW ROOMS (bolts must fly in every dimension the player reaches — this map is
// a stale twin of movement.walkable; add rooms here too or the gun dies on spawn):
const tomb = x >= -4.6 + 0.12 && x <= 4.6 - 0.12 && z >= 92 + 0.12 && z <= 102 - 0.12 // TOMB DIMENSION
const ldimP = x >= -5 + 0.12 && x <= 5 - 0.12 && z >= 56.5 && z <= 67.5 // LURKER DIMENSION
const denI = Math.abs(x) <= 5.6 - 0.12 && z >= 76.9 && z <= 87.2 && Math.hypot(x, z - 82) > 1.0 // DEN INTERIOR (column solid)
return ((main || side || door) && !blocked(x, z, warp)) || hall || vch || tomb || ldimP || denI
}
const wd = sim.worldData
const V = wd.__vf
const step = Math.min(dt, 1 / 30)
if (!V.bolts) V.bolts = []
if (V.ammo == null) V.ammo = 24
if (V.reload == null) V.reload = 0
V.reload = Math.max(0, V.reload - step)
const inp = wd.input || {}
const firing = !!(inp.pointer && inp.pointer.pressed) // click to fire (Space is jump now)
if (V.weapon === 2 && firing && V.reload <= 0 && V.ammo > 0) { // gun fires only when SELECTED (weapon 2)
const yaw = V.yaw || 0, pitch = V.pitch || 0
const cp = Math.cos(pitch), sp = Math.sin(pitch)
const fx = Math.sin(yaw) * cp, fy = sp, fz = Math.cos(yaw) * cp // FULL 3D look dir (was yaw-only → shots flew flat over low enemies)
const ey = (V.py != null ? V.py : 1.7) - 0.15 // from just below the eye
const SPD = 24
V.bolts.push({ x: (V.px || 0) + fx * 0.4, y: ey + fy * 0.4, z: (V.pz || 0) + fz * 0.4, dx: fx * SPD, dy: fy * SPD, dz: fz * SPD, life: 1.4, bounced: 0 })
V.ammo -= 1; V.reload = 0.13; V.muzzle = 1.0
}
V.muzzle = Math.max(0, (V.muzzle || 0) - step * 6)
// context for the bolt/wall test — same signals movement wrote this frame
const warp = V.warp || 0
// bolts see the FULLY-GROWN corridor always: cL is the door-growth ANIMATION
// state — when it is small (fresh session / respawn) the corridor's far half
// (where the pixel weave begins, z -40) read as a WALL and shots bounced at the
// muzzle inside pixelland (Galen). The corridor's final geometry is fixed.
const L = Math.max(V.cL != null ? V.cL : 6, 28)
const uu = Array.isArray(wd.gpuUniforms) ? wd.gpuUniforms : null
const doorOpen = !!(uu && (uu[45] || 0) >= 0.95)
const REST = 0.85 // restitution — a touch of speed lost on the bounce feels right
const FLOOR_Y = 0.06, CEIL_Y = 11.5 // bolt bounces off floor + ceiling too
const alive = []
for (const b of V.bolts) {
b.life -= step
if (b.life <= 0) continue
if (b.bounced == null) b.bounced = 0
if (b.dy == null) b.dy = 0
const nx = b.x + b.dx * step
const ny = b.y + b.dy * step
const nz = b.z + b.dz * step
const floorHit = ny < FLOOR_Y
const ceilHit = ny > CEIL_Y
const wallHit = !walkable(nx, nz, warp, L, doorOpen)
if (!floorHit && !ceilHit && !wallHit) {
b.x = nx; b.y = ny; b.z = nz // clear flight
} else if (b.bounced) {
b.life = 0; continue // already bounced → dies on any surface
} else {
// reflect off whatever we'd cross. Walls flip the horizontal axis (test each
// independently; a concave corner flips both); floor/ceiling flip vertical.
if (floorHit || ceilHit) b.dy = -b.dy * REST
if (wallHit) {
const hitX = !walkable(nx, b.z, warp, L, doorOpen)
const hitZ = !walkable(b.x, nz, warp, L, doorOpen)
if (hitX && !hitZ) { b.dx = -b.dx * REST }
else if (hitZ && !hitX) { b.dz = -b.dz * REST }
else { b.dx = -b.dx * REST; b.dz = -b.dz * REST }
}
// advance the axes that stayed free, so a floor skim keeps travelling instead of freezing
if (!wallHit) { b.x = nx; b.z = nz }
if (!floorHit && !ceilHit) { b.y = ny }
b.bounced = 1
}
alive.push(b)
V.pop.push(b.x, b.y, b.z, 3.0, Math.max(0, Math.min(1, b.life / 1.4)), 0, 0, 0)
}
V.bolts = alive
const u = wd.gpuUniforms
if (u) { u[7] = V.ammo; u[15] = V.muzzle }
}
__C['vf-weapon-bolts'] = projectiles
__C['vf-weapon-bolts@rev'] = __REV
}
// weapons WORK everywhere (Galen: "we want weapons in pixelated areas actually" —
// the earlier warp-out was a bug report, not a design). Heal stashed sessions.
const __V9 = __wd.__vf
if (__V9 && __V9.__wpStash) { __V9.weapon = __V9.__wpStash.weapon; __V9.__wpStash = null }
if (__V9 && Array.isArray(__wd.gpuUniforms)) __wd.gpuUniforms[49] = 0
try { __C['vf-weapon-bolts'](sim, dt) } catch (e) {}
})()hook · vf-weapons
by weapons-carve
// vf-weapons — RETIRED (Galen, Aug 9): weapon selection + ownership + u[69]/u[70]
// were carved into the dedicated authority node vf-weapon-select, which runs LAST
// as the single final word on V.weapon. The number keys were flickering because
// this node did not reliably run last against the other weapon writers. No-op kept
// for the registry slot so nothing that references this id breaks.
try { /* selection now lives entirely in vf-weapon-select */ } catch (e) {}
hook · vf-nave-orb
by claude-code
;(() => {
// vf-nave-orb — THE NAVE — the MANIFOLD orb sentinel: idle/coil/lash/relocate state machine, 4 advecting blobs (u20-42)
// Carved from the veilfire monolith; geometry via the worker-global __VF_GEO (vf-frame publishes it;
// NEVER in worldData — functions DataCloneError the sandbox sync). The subsystem is BUILT ONCE per
// worker and cached in __VF_FNS (keyed on this node's rev + the geo rev) — per-tick cost is one call,
// not a re-declaration of the whole section (the monolith re-declared everything every tick).
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = '1cf82455fc/' + globalThis.__VF_GEO_REV
if (__C['vf-nave-orb@rev'] !== __REV) {
const { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH } = globalThis.__VF_GEO
// orb (node: orb-ai, v3) — the MANIFOLD sentinel. ORB CONTRACT v3 (swarm/SPEC.md):
// the hook is the state machine + REAL PHYSICS driver; the shader owns geometry.
//
// idle → coil (charge, uni20) → LASH (curved whip, uni23 + side uni41)
// → ~55% RELOCATE: dissolve (flow uni24 0→1 at old home) → travel (4 blobs
// advect with momentum + curl swirl — the FLUID pattern at blob scale —
// one pass biased NEAR THE PLAYER, then converge on a NEW anchor)
// → reform (flow 1→0 at the new home; uni26..28 = home moves)
// → else cooldown → idle
//
// Rows owned: 20 charge · 21 flash · 23 lash · 24 flow · 25 mode · 26-28 home ·
// 29-40 blob pos ×4 · 41 whip side. Row 22 (death-fade) NEVER touched.
// Damage: one hit per lash at the whipcrack if the player is in reach of the
// CURRENT home. Never throws, never NaN.
const DEF_HOME = { x: 0.0, y: 3.2, z: 4.0 }
// anchors the orb can reform at — nave spots clear of columns (|x|<=2.0) + dais air
const ANCHORS = [
{ x: 0.0, y: 3.2, z: 1.0 },
{ x: 0.0, y: 3.1, z: 6.0 },
{ x: 1.9, y: 2.8, z: -2.5 },
{ x: -1.9, y: 2.8, z: 3.5 },
]
const SENSE = 6.5, CHARGE_T = 2.8, CHARGE_DECAY = 0.5
const LASH_OUT = 0.3, LASH_BACK = 0.9, LASH_REACH = 5.4, LASH_YBAND = 2.4, LASH_DMG = 0.14
const RELOC_CHANCE = 0.55, DISSOLVE_T = 1.1, REFORM_T = 1.3, COOL_T = 2.2
const TRAVEL_MIN = 3.5 // s before blobs may settle
const STALK_AFTER = 8.0 // s idle + player out of range → hunt the player
const MODE = { idle: 0, coil: 1, lash: 2, dissolve: 3, travel: 3, reform: 4, cooldown: 0 }
const fin = (v, d) => (Number.isFinite(v) ? v : d)
const clamp01 = (v) => (Number.isFinite(v) ? (v < 0 ? 0 : v > 1 ? 1 : v) : 0)
// cheap deterministic 3D curl-ish swirl (divergence-poor by construction):
// derivatives of two scalar sin fields — momentum + this = fluid-looking motion
function swirl(x, y, z, t) {
const a = 1.7 * Math.sin(0.9 * y + 1.3 * z + t * 0.9) + 0.8 * Math.sin(2.1 * z - t * 0.7)
const b = 1.7 * Math.sin(0.9 * z + 1.1 * x - t * 0.8) + 0.8 * Math.sin(1.9 * x + t * 0.6)
const c = 1.7 * Math.sin(1.0 * x + 1.2 * y + t * 0.7) + 0.8 * Math.sin(2.3 * y - t * 0.9)
return [b - c, c - a, a - b]
}
function orb(sim, dt) {
try {
const wd = sim && sim.worldData
if (!wd) return
if (!wd.__vf) wd.__vf = {}
const V = wd.__vf
const step = Math.max(0, Math.min(fin(dt, 1 / 60), 1 / 30))
if (!V.hits) V.hits = []
if (!V.orb || V.orb.ver !== 3) {
// per-session seed (Math.random when the sandbox allows) — a predictable
// horror is not one; sim.rand() still wins when the world opts into determinism
let s0 = 22695477
try { s0 = (Math.floor(Math.random() * 0x7fffffff) | 1) } catch (e) {}
V.orb = {
ver: 3, st: 'idle', charge: 0, lash: 0, lashT: 0, flow: 0, flash: 0, cd: 0,
hitDone: true, cracked: true, side: 1, seed: s0, t: 0, anchor: 0, idleT: 0, retract: 0,
home: { ...DEF_HOME }, dest: { ...DEF_HOME }, travelT: 0, stalk: false,
blobs: [0, 1, 2, 3].map(() => ({ x: DEF_HOME.x, y: DEF_HOME.y, z: DEF_HOME.z, vx: 0, vy: 0, vz: 0 })),
}
}
const O = V.orb
O.t += step
const rnd = () => {
if (sim && typeof sim.rand === 'function') { const r = sim.rand(); if (Number.isFinite(r)) return r }
O.seed = (Math.imul(O.seed, 1103515245) + 12345) & 0x7fffffff
return O.seed / 0x7fffffff
}
// launch the dissolve→travel→reform pipeline toward a destination
const disperse = (dest, stalk) => {
O.st = 'dissolve'; O.dest = { ...dest }; O.stalk = !!stalk; O.travelT = 0; O.idleT = 0
for (const b of O.blobs) {
b.x = O.home.x; b.y = O.home.y; b.z = O.home.z
const th = rnd() * 6.28318, phv = rnd() * 2 - 1
b.vx = Math.cos(th) * 2.4; b.vy = phv * 1.4; b.vz = Math.sin(th) * 2.4
}
}
const px = fin(V.px, 0), pz = fin(V.pz, -6), py = fin(V.py, 1.7)
const H = O.home
const dp = Math.hypot(px - H.x, pz - H.z)
if (O.st === 'idle' || O.st === 'coil') {
O.retract = 0
if (dp < SENSE) {
O.idleT = 0
O.st = 'coil'
O.charge = Math.min(1, O.charge + step / CHARGE_T)
if (O.charge >= 1) { O.st = 'lash'; O.lashT = 0; O.lash = 0; O.hitDone = false; O.cracked = false; O.side = rnd() < 0.5 ? -1 : 1 }
} else {
O.charge = Math.max(0, O.charge - step * CHARGE_DECAY)
if (O.charge <= 0) O.st = 'idle'
// STALK: the player left its range — after a while it comes HUNTING:
// relocate to the anchor nearest the player (never looks dead, finds you)
O.idleT += step
if (O.idleT > STALK_AFTER) {
let best = 0, bd = 1e9
for (let a = 0; a < ANCHORS.length; a++) {
const d2 = Math.hypot(px - ANCHORS[a].x, pz - ANCHORS[a].z)
if (d2 < bd) { bd = d2; best = a }
}
if (best !== O.anchor) { O.anchor = best; disperse(ANCHORS[best], true) }
else O.idleT = 0 // already at the nearest — rest
}
}
} else if (O.st === 'lash') {
O.lashT += step
if (O.lashT < LASH_OUT) {
O.lash = O.lashT / LASH_OUT
O.retract = 0
} else if (O.lashT < LASH_OUT + LASH_BACK) {
if (!O.cracked) { O.cracked = true; O.flash = 1.0 }
O.retract = 1 // the DRAG BACK — shader sags the tendril
O.lash = 1 - (O.lashT - LASH_OUT) / LASH_BACK
} else {
O.lash = 0
O.retract = 0
// charge is NOT zeroed — it RELAXES through the next states so the coil
// eases open instead of snapping to the default form
if (rnd() < RELOC_CHANCE) {
// choose a DIFFERENT anchor to reform at
let a = Math.floor(rnd() * ANCHORS.length) % ANCHORS.length
if (a === O.anchor) a = (a + 1 + Math.floor(rnd() * (ANCHORS.length - 1))) % ANCHORS.length
O.anchor = a
disperse(ANCHORS[a], false)
} else { O.st = 'cooldown'; O.cd = COOL_T }
}
if (!O.hitDone && O.lash > 0.7 && dp <= LASH_REACH && Math.abs(py - 2.6) < LASH_YBAND) {
V.hits.push({ dmg: LASH_DMG, x: px, z: pz })
O.hitDone = true
}
} else if (O.st === 'dissolve') {
O.charge = Math.max(0, O.charge - step * 0.8) // ease the coil open
O.flow = Math.min(1, O.flow + step / DISSOLVE_T)
if (O.flow >= 1) { O.st = 'travel'; O.travelT = 0 }
} else if (O.st === 'travel') {
O.charge = Math.max(0, O.charge - step * 0.8)
O.travelT += step
// waypoint: first sweep NEAR THE PLAYER (the terror pass), then the new anchor
const nearPlayer = O.travelT < TRAVEL_MIN * 0.55
const atSpawn = (Math.abs(px) < 2 && pz < -4.5 && pz > -7.5) // don't loom on the player at spawn
const tgt = (nearPlayer && !atSpawn) ? { x: px, y: 2.1, z: pz } : O.dest
let settled = 0
for (const b of O.blobs) {
const sw = swirl(b.x, b.y, b.z, O.t)
const txv = tgt.x - b.x, tyv = tgt.y - b.y, tzv = tgt.z - b.z
const td = Math.hypot(txv, tyv, tzv) || 1e-4
// near-player pass ORBITS rather than lands: damp the spring inside 1.6u
const pull = nearPlayer && td < 1.6 ? 0.4 : 2.2
b.vx += (sw[0] * 1.5 + (txv / td) * pull) * step
b.vy += (sw[1] * 0.9 + (tyv / td) * pull) * step
b.vz += (sw[2] * 1.5 + (tzv / td) * pull) * step
// separation so the swarm reads as a swarm
for (const o of O.blobs) {
if (o === b) continue
const ox = b.x - o.x, oy = b.y - o.y, oz = b.z - o.z
const od = Math.hypot(ox, oy, oz)
if (od > 1e-3 && od < 0.9) { const w = (0.9 - od) * 3.0; b.vx += (ox / od) * w * step; b.vy += (oy / od) * w * step; b.vz += (oz / od) * w * step }
}
const dampf = Math.max(0, 1 - 1.1 * step)
b.vx *= dampf; b.vy *= dampf; b.vz *= dampf
b.x += b.vx * step; b.y += b.vy * step; b.z += b.vz * step
// soft bounds — the nave interior (blob surface ~0.6)
if (b.x > 2.6) { b.x = 2.6; b.vx = -Math.abs(b.vx) }
if (b.x < -2.6) { b.x = -2.6; b.vx = Math.abs(b.vx) }
if (b.y > 3.4) { b.y = 3.4; b.vy = -Math.abs(b.vy) }
if (b.y < 1.2) { b.y = 1.2; b.vy = Math.abs(b.vy) }
if (b.z > 7.9) { b.z = 7.9; b.vz = -Math.abs(b.vz) }
if (b.z < -7.4) { b.z = -7.4; b.vz = Math.abs(b.vz) }
if (!nearPlayer && td < 0.5) settled++
}
if (O.travelT > TRAVEL_MIN && settled >= 3) {
O.home = { ...O.dest } // THE ORB HAS MOVED
O.st = 'reform'
}
if (O.travelT > 12) { O.home = { ...O.dest }; O.st = 'reform' } // failsafe
} else if (O.st === 'reform') {
O.charge = Math.max(0, O.charge - step * 0.8)
O.flow = Math.max(0, O.flow - step / REFORM_T)
// blobs spiral into the new home as the body regrows
for (const b of O.blobs) {
b.x += (O.home.x - b.x) * 4.0 * step
b.y += (O.home.y - b.y) * 4.0 * step
b.z += (O.home.z - b.z) * 4.0 * step
}
if (O.flow <= 0) { O.st = 'cooldown'; O.cd = COOL_T }
} else if (O.st === 'cooldown') {
O.charge = Math.max(0, O.charge - step * 0.8) // the coil eases open
O.cd -= step
if (O.cd <= 0 && O.charge <= 0.02) { O.cd = 0; O.st = 'idle' }
} else {
O.st = 'idle'
}
O.flash = Math.max(0, O.flash - step * 3.0)
const u = wd.gpuUniforms
if (Array.isArray(u) && u.length >= 43) {
u[20] = clamp01(O.charge)
u[21] = clamp01(O.flash)
u[23] = clamp01(O.lash)
u[24] = clamp01(O.flow)
u[25] = MODE[O.st] ?? 0
u[26] = fin(O.home.x, DEF_HOME.x); u[27] = fin(O.home.y, DEF_HOME.y); u[28] = fin(O.home.z, DEF_HOME.z)
for (let i = 0; i < 4; i++) {
const b = O.blobs[i]
u[29 + i * 3] = fin(b.x, O.home.x); u[30 + i * 3] = fin(b.y, O.home.y); u[31 + i * 3] = fin(b.z, O.home.z)
}
u[41] = O.side === -1 ? -1 : 1
u[42] = O.retract ? 1 : 0
}
} catch (e) { /* composed with 7 siblings — never throw */ }
}
__C['vf-nave-orb'] = orb
__C['vf-nave-orb@rev'] = __REV
}
// ── THE UPGRADED CRYSTAL vs THE MANIFOLD (Galen): with the dragon's crystal
// held, bolts wound the orb (8 hp, white sparks per hit); at 0 it dissolves
// FOR GOOD — the sentinel is gone, its uniforms zeroed, its strikes ended. ──
const __V = __wd.__vf
if (!__V.orbDead && __V.hasCrystal && __V.orb && Array.isArray(__V.orb.blobs) && Array.isArray(__V.bolts)) {
if (__V.orbHp == null) __V.orbHp = 8
outer: for (const __b of __V.bolts) {
if (__b.life <= 0) continue
for (const __bl of __V.orb.blobs) {
if (Math.hypot(__b.x - __bl.x, __b.y - __bl.y, __b.z - __bl.z) < 1.5) {
__b.life = 0; __V.orbHp -= 1
for (let __s2 = 0; __s2 < 6; __s2++) __V.pop.push(__bl.x + Math.sin(__s2 * 1.05) * 0.5, __bl.y + Math.cos(__s2 * 1.3) * 0.5, __bl.z + Math.sin(__s2 * 2.2) * 0.5, 3, 1, 0, 0, 0)
__wd.__play_sound = [{ frequency: 700, duration: 0.09, volume: 0.2, type: 'square' }]
if (__V.orbHp <= 0) {
__V.orbDead = true
for (const __bl2 of __V.orb.blobs) for (let __s3 = 0; __s3 < 10; __s3++) __V.pop.push(__bl2.x + Math.sin(__s3 * 0.63) * (0.3 + __s3 * 0.12), __bl2.y + Math.cos(__s3 * 0.9) * 0.4, __bl2.z + Math.cos(__s3 * 0.63) * (0.3 + __s3 * 0.12), 5, 0.9, 0, 0, 0)
__wd.__play_sound = [{ frequency: 160, duration: 0.7, volume: 0.3, type: 'sine' }, { frequency: 64, duration: 1.1, volume: 0.3, type: 'triangle' }]
break outer
}
}
}
}
}
if (__V.orbDead) {
const __u = __wd.gpuUniforms
if (Array.isArray(__u)) { for (let __i = 20; __i <= 42; __i++) __u[__i] = 0 }
} else {
try { __C['vf-nave-orb'](sim, dt) } catch (e) {}
}
})()hook · vf-crystalflow
by claude-code
// VEILFIRE-3D · WEAPON 1 = CRYSTAL. Held graphic (u71), no gun. Click streams
// wireframe cubes to the ORB (u60-66) + DAMAGES it. THE SPLIT IS HARMLESS: while
// the orb is a swarm (dissolve/travel/reform) it cannot charge or lash — so it
// never "lashes from the coil" and never hurts you while split. Additive.
try {
const wd=sim.worldData, V=wd.__vf, u=wd.gpuUniforms
if (V && Array.isArray(u) && u.length>=72){
const sdt=Math.min(dt||0.016,1/30)
const O=V.orb
const rnd=()=>{ try{ if(sim&&typeof sim.rand==='function'){const r=sim.rand(); if(Number.isFinite(r))return r;} }catch(e){} return 0.5 }
// ── SPLIT IS HARMLESS (always enforced, even for a natural relocate) ──
const splitting = O && (O.st==='dissolve'||O.st==='travel'||O.st==='reform'||(O.flow||0)>0.05)
if (splitting){ O.charge=0; O.lash=0; O.hitDone=true } // no charge buildup, no pending lash hit
// ── WEAPON 1 = crystal: held graphic + suppress the gun ──
const isCry = (V.weapon===1)
u[71] = isCry ? 1 : 0
u[72] = (V.weapon===2) ? 1 : 0 // gun viewmodel flag (cflow draws it)
if (isCry){ if (Array.isArray(V.bolts)) V.bolts.length=0; u[15]=0 }
// ── aim: project the orb centroid to screen ──
const inp=wd.input||{}, ptr=inp.pointer||{}
const ro=[u[240],u[241],u[242]], fov=u[243]||1.2, ta=[u[244],u[245],u[246]]
const sub=(a,b)=>[a[0]-b[0],a[1]-b[1],a[2]-b[2]]
const dt3=(a,b)=>a[0]*b[0]+a[1]*b[1]+a[2]*b[2]
const crs=(a,b)=>[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]
const nrm=(v)=>{const l=Math.hypot(v[0],v[1],v[2])||1;return[v[0]/l,v[1]/l,v[2]/l]}
const fw=nrm(sub(ta,ro)), rgt=nrm(crs([0,1,0],fw)), up=crs(fw,rgt)
let tx=null,ty=null,onS=false
if(O&&Array.isArray(O.blobs)&&O.blobs.length){
let cx=0,cy=0,cz=0;for(const b of O.blobs){cx+=b.x;cy+=b.y;cz+=b.z}
const n=O.blobs.length,W=[cx/n,cy/n,cz/n]
const d=sub(W,ro),df=dt3(d,fw)
if(df>0.3){const xc=dt3(d,rgt)/df,yc=dt3(d,up)/df
tx=(1-xc/fov)*0.5; ty=(1-yc/fov)*0.5
onS=tx>-0.05&&tx<1.05&&ty>-0.05&&ty<1.05}
}
const firing=!!(ptr&&ptr.pressed)
const edge=firing&&!V._cfPrev
V._cfPrev=firing
if(edge && isCry && onS){ V._cfT=0.7; V._cfPhase=0; V._cfTx=tx; V._cfTy=ty }
if((V._cfT||0)>0){
V._cfT=Math.max(0,V._cfT-sdt)
V._cfPhase=((V._cfPhase||0)+sdt*1.6)%1
if(onS){V._cfTx=tx;V._cfTy=ty}
u[100]=Math.min(1,V._cfT/0.35)
u[101]=0.5; u[102]=0.9
u[103]=Math.max(0,Math.min(1,V._cfTx)); u[104]=Math.max(0,Math.min(1,V._cfTy))
u[105]=V._cfPhase; u[106]=8
// DAMAGE the orb: flash + drain a charge -> a CLEAN harmless split
if(O && onS){
u[21]=Math.min(1,(u[21]||0)+0.5)
if(O.chp==null) O.chp=6
O.chp -= sdt*3.0
if(O.chp<=0){
O.chp=6
O.st='dissolve'; O.charge=0; O.lash=0; O.hitDone=true; O.flow=0; O.travelT=0; O.idleT=0
if(Array.isArray(O.blobs)&&O.home) for(const b of O.blobs){
b.x=O.home.x;b.y=O.home.y;b.z=O.home.z
const th=rnd()*6.2832, phv=rnd()*2-1
b.vx=Math.cos(th)*2.4;b.vy=phv*1.4;b.vz=Math.sin(th)*2.4
}
wd.__play_sound=[{frequency:520,duration:0.18,volume:0.25,type:'sine'},{frequency:210,duration:0.22,volume:0.15,type:'triangle'}]
}
}
} else { u[100]=0 }
}
} catch(e){}
hook · vf-ambush-room
by claude-code
// vf-ambush-room — THE HIDDEN AMBUSHER ROOM (first client of the ix interaction
// layer). This node DECLARES its bounds as a field + a reveal reaction; it never
// reads another node's uniforms and never touches the shader. The room's geometry
// exists ONLY where a 'reality' field (the thrown lantern) overlaps the declared
// box — the ix engine + ix module compose the intersection. On overlap ENTER the
// ambushers wake: a sting, then three watchers appear inside the room while the
// reveal holds. Recall the lantern → room and watchers are gone.
// The ROOM constant below is the ONLY place these coordinates exist — not in any
// shader, not in any other node.
try {
const wd = sim.worldData, V = wd.__vf
if (V) {
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} })
// nave open patch: between the ±3.2 colonnade, short of the dais steps (z≥6)
const ROOM = { x: 0, y: 1.55, z: 3.4, hx: 2.1, hy: 1.55, hz: 2.1 }
IX.fields['ambroom.bounds'] = {
node: 'vf-ambush-room', tag: 'room-bounds', shape: 'box', active: true,
x: ROOM.x, y: ROOM.y, z: ROOM.z, hx: ROOM.hx, hy: ROOM.hy, hz: ROOM.hz
}
const r = IX.reactions['ambroom.reveal'] = Object.assign(
IX.reactions['ambroom.reveal'] || {},
{ node: 'vf-ambush-room', whenTag: 'reality', myField: 'ambroom.bounds', effect: 'reveal', region: 1 })
if (!V.__ambRoom) V.__ambRoom = { woke: 0, stung: 0, amb: null, burst: 0, lastBeat: -1 }
const A = V.__ambRoom
// liveness gate: a frozen engine (beat stopped) must read as "no overlap",
// not as latched-on reveals (sub:ix-review finding #1)
const engineAlive = IX.beat != null && IX.beat !== A.lastBeat
A.lastBeat = IX.beat != null ? IX.beat : A.lastBeat
const on = !!r.on && engineAlive
if (r.justEntered) {
r.justEntered = false // consume the edge — fires ONCE even if the engine stalls
A.woke = 1
A.burst = 1
if (!A.amb) A.amb = [
{ x: ROOM.x - 1.1, z: ROOM.z + 0.9, phase: 0 },
{ x: ROOM.x + 1.2, z: ROOM.z + 0.4, phase: 2.1 },
{ x: ROOM.x - 0.2, z: ROOM.z - 1.2, phase: 4.4 }]
}
// sting retries until the sound slot is actually free (woke alone would
// permanently swallow it if another node held __play_sound that tick)
if (A.woke && !A.stung && wd.__play_sound == null) {
A.stung = 1
wd.__play_sound = [
{ frequency: 96, duration: 0.5, volume: 0.3, type: 'sawtooth' },
{ frequency: 640, duration: 0.18, volume: 0.2, type: 'square' }]
}
if (on && A.amb && Array.isArray(V.pop)) {
const px = V.px || 0, pz = V.pz || 0
const sdt = Math.min(dt || 0.016, 1 / 30)
A.burst = Math.max(0, A.burst - sdt * 1.6)
for (const a of A.amb) {
a.phase += sdt * 2.2
const yaw = Math.atan2(px - a.x, pz - a.z) // watchers track the player
V.pop.push(a.x, 0, a.z, 2, 1, a.phase % 1000, Number.isFinite(yaw) ? yaw : 0, A.burst)
}
}
}
} catch (e) {}
hook · vf-lantern
by claude-code
// vf-lantern — THE LANTERN + its FLAME (Galen). Slot 1. The LANTERN stays in your hand;
// CLICK throws the little FLAME from inside it — a GENTLE arc, ONE little bounce, then it
// settles and casts the reality reveal-sphere from where it lands. CLICK AGAIN = call the
// flame back. HELD (no flame out) = a weak short cone peek. Sounds are ONE-SHOT per event
// (throw / single bounce / settle / recall) — never per frame. Owns u121-127 + u128-132.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
const inp = wd.input || {}, ptr = inp.pointer || {}
if (!V.lantern) V.lantern = { deployed: false, x: 0, y: 1.5, z: 0, dx: 0, dy: 0, dz: 1, fireEdge: false, rad: 0, settling: true }
const L = V.lantern
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
const yaw = V.yaw || 0, pitch = V.pitch || 0
const cp = Math.cos(pitch)
const fx = Math.sin(yaw) * cp, fy = Math.sin(pitch), fz = Math.cos(yaw) * cp
const holdSlot = (V.weapon === 1)
const click = !!ptr.pressed && !L.fireEdge
L.fireEdge = !!ptr.pressed
const wasDeployed = !!L.deployed
const dtc = dt || 0.016
let throwEv = false, settleEv = false, bounceEv = false, recallEv = false
if (holdSlot && click) {
if (!wasDeployed) { // THROW THE FLAME (gentle lob)
L.deployed = true; L.rad = 0; L.settling = false; L.bounces = 0
const rgx = Math.cos(yaw), rgz = -Math.sin(yaw) // camera right (horizontal)
L.x = px + fx * 0.45 - rgx * 0.22; L.y = py - 0.35 + fy * 0.45; L.z = pz + fz * 0.45 - rgz * 0.22 // leaves the lantern (lower-left of view)
const SP = 11.5
L.vx = fx * SP; L.vy = fy * SP + 3.4; L.vz = fz * SP
throwEv = true
} else { L.deployed = false; L.rad = 0; recallEv = true } // CALL IT BACK
}
// the LANTERN never leaves your hand — held cone origin rides the hand
L.hx = px + fx * 0.5; L.hy = py - 0.15 + fy * 0.5; L.hz = pz + fz * 0.5
if (L.deployed && !L.settling) {
const GEO = globalThis.__VF_GEO, walk = (GEO && GEO.walkable) ? GEO.walkable : null
const steps = 3, h = dtc / steps
for (let s = 0; s < steps; s++) {
L.vy -= 20 * h
let nx = L.x + L.vx * h, ny = L.y + L.vy * h, nz = L.z + L.vz * h
if (walk && (!walk(nx, L.z, V.warp || 0, 0, 1) || !walk(L.x, nz, V.warp || 0, 0, 1))) { L.settling = true; settleEv = true; break } // hit a wall → STICK
if (ny < 0.35) { // floor
ny = 0.35
if (L.bounces < 1) { L.vy = Math.abs(L.vy) * 0.32; L.vx *= 0.6; L.vz *= 0.6; L.bounces = 1; bounceEv = true } // ONE little bounce
else { L.vx = 0; L.vy = 0; L.vz = 0; L.settling = true; settleEv = true } // then rest
}
L.x = nx; L.y = ny; L.z = nz
}
if (!L.settling && Math.hypot(L.vx, L.vy, L.vz) < 1.8) { L.vx = 0; L.vy = 0; L.vz = 0; L.settling = true; settleEv = true }
}
if (L.deployed && Array.isArray(V.pop)) { // render the little FLAME (flickering embers)
L.rad = Math.min(4.2, (L.rad || 0) + dtc * 7)
const tt = V.t || 0
V.pop.push(L.x, L.y, L.z, 12, 0.9, 0, 0, 0) // the PURPLE CRYSTAL
for (let i = 0; i < 2; i++) V.pop.push(L.x + Math.sin(tt * 7 + i * 3.1) * 0.1, L.y + 0.1 + Math.cos(tt * 6 + i) * 0.08, L.z + Math.cos(tt * 7 + i * 3.1) * 0.1, 12, 0.42, 0, 0, 0) // violet sparkles
}
// ONE-SHOT sounds — never per frame
if (throwEv) wd.__play_sound = [{ frequency: 300, duration: 0.14, volume: 0.18, type: 'sawtooth' }, { frequency: 760, duration: 0.10, volume: 0.14, type: 'sine' }]
else if (settleEv) wd.__play_sound = [{ frequency: 330, duration: 0.40, volume: 0.16, type: 'sine' }, { frequency: 494, duration: 0.36, volume: 0.10, type: 'triangle' }, { frequency: 660, duration: 0.50, volume: 0.08, type: 'sine' }]
else if (bounceEv) wd.__play_sound = [{ frequency: 440, duration: 0.05, volume: 0.12, type: 'triangle' }]
else if (recallEv) wd.__play_sound = [{ frequency: 820, duration: 0.09, volume: 0.15, type: 'sine' }, { frequency: 520, duration: 0.12, volume: 0.11, type: 'triangle' }]
const holding = holdSlot && !L.deployed
u[121] = holding ? 1 : 0
u[122] = L.hx; u[123] = L.hy; u[124] = L.hz
u[125] = fx; u[126] = fy; u[127] = fz
u[128] = L.deployed ? 1 : 0
u[129] = L.x; u[130] = L.y; u[131] = L.z
u[132] = L.rad || 0
// ── ix: declare the lantern's REALITY field into the node⋈node interaction
// registry (folded in from Fable's bridging declarer, hand-off Aug 6 2026).
// Fresh object every tick — the ix-engine stale-sweep requires it.
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} })
IX.fields['lantern.reality'] = { node: 'vf-lantern', tag: 'reality', shape: 'sphere',
active: !!L.deployed, x: L.x, y: L.y, z: L.z, r: L.rad || 0 }
}
} catch (e) {}
hook · ix-engine
by claude-code
// ix-engine — NODE⋈NODE INTERACTION EFFECTS (engine-category seed v1, Galen's brief
// Aug 6 2026). THE PRIMITIVE: nodes declare FIELDS ({tag, shape, transform}) and
// REACTIONS ({whenTag, effect}) in the worker-global registry globalThis.__IX
// (worker-global by LAW — functions/objects never enter sim.worldData). Each tick:
// 1. broad-phase AABB reject, then exact sphere∩box overlap per reaction
// 2. set reaction state: on / justEntered / justExited — the DECLARING node reads
// its own reaction and fires its gameplay there, not here
// 3. publish ACTIVE 'reveal' overlaps as records in the OWNED lane u150-175 —
// the ix shader module renders declared geometry clipped to the overlap.
// Record: u150 = count · slot base b=151+s*12: [region, coverType, cx,cy,cz, cr,
// gx,gy,gz, hx,hy,hz]. Capacity 2 — extras are console-warned, never silently eaten.
try {
const wd = sim.worldData, u = wd.gpuUniforms
if (Array.isArray(u)) {
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} })
const F = IX.fields, R = IX.reactions
// heartbeat — consumers gate liveness on this changing (a dead engine must
// read as "no interactions", never as frozen-true reactions)
IX.beat = (IX.beat || 0) + 1
// stale sweep — declarers replace their field object every tick; a field
// still carrying last round's stale mark has lost its declarer (world swap,
// removed node) and is deleted. Kills ghost reveals from a previous session.
for (const fid in F) {
if (F[fid].__stale) { delete F[fid]; continue }
F[fid].__stale = true
}
const setState = (r, on, other) => {
r.justEntered = on && !r.on
r.justExited = !on && !!r.on
r.on = on
r.other = on ? other : null
}
let slot = 0, want = 0
for (const rid in R) {
const r = R[rid]
const mine = F[r.myField]
// v1 contract: my field must be a box, covers must be spheres — anything
// else is SKIPPED (honest no-op), never mis-mathed as the wrong shape.
if (!mine || mine.active === false || mine.shape !== 'box') { setState(r, false); continue }
let hit = null
for (const fid in F) {
const f = F[fid]
if (f === mine || f.tag !== r.whenTag || f.active === false || f.shape !== 'sphere') continue
const rr = f.r || 0
// broad-phase AABB reject
if (Math.abs(f.x - mine.x) > mine.hx + rr ||
Math.abs(f.y - mine.y) > mine.hy + rr ||
Math.abs(f.z - mine.z) > mine.hz + rr) continue
// exact sphere∩box: closest point on the box to the sphere center
const cx = Math.max(mine.x - mine.hx, Math.min(f.x, mine.x + mine.hx))
const cy = Math.max(mine.y - mine.hy, Math.min(f.y, mine.y + mine.hy))
const cz = Math.max(mine.z - mine.hz, Math.min(f.z, mine.z + mine.hz))
const dx = f.x - cx, dy = f.y - cy, dz = f.z - cz
if (dx * dx + dy * dy + dz * dz <= rr * rr) { hit = f; break }
}
setState(r, !!hit, hit)
// EXPOSE ramp (Galen: "world revealed raymarches from lantern bounds to
// player vision") — while the overlap holds, the published cover radius
// GROWS until the whole declared box is inside the reveal; the room
// develops outward from the flame instead of showing a sphere-sliver.
const dtc = (typeof dt === 'number' ? dt : 0.016)
r.expose = Math.max(0, Math.min(1, (r.expose || 0) + (hit ? dtc * 1.1 : -dtc * 2.5)))
if (hit && r.effect === 'reveal') {
want++
if (slot < 2) {
const b = 151 + slot * 12
u[b] = r.region || 0; u[b + 1] = 1
u[b + 2] = hit.x; u[b + 3] = hit.y; u[b + 4] = hit.z
// expanded cover: at expose=1 the sphere swallows the box from the flame's position
const reach = Math.hypot(mine.hx, mine.hy, mine.hz) + Math.hypot(hit.x - mine.x, hit.y - mine.y, hit.z - mine.z)
u[b + 5] = (hit.r || 0) + (r.expose || 0) * reach
u[b + 6] = mine.x; u[b + 7] = mine.y; u[b + 8] = mine.z
u[b + 9] = mine.hx; u[b + 10] = mine.hy; u[b + 11] = mine.hz
slot++
}
}
}
u[150] = slot
if (want > 2 && !IX.__capWarned) {
IX.__capWarned = 1
try { console.warn('[ix] ' + want + ' active reveals, capacity 2 — extras dropped') } catch (e) {}
}
}
} catch (e) {}
hook · vf-tomb-steam
by claude-code
;(() => {
// vf-tomb-steam — FIELD-TINT effect class (v1, Claude Opus, Aug 6 2026): the SECOND effect
// type on the ix overlap primitive (sibling of geometry-reveal). A cold STEAM pocket in the
// CRYPT — where the thrown lantern's reality sphere overlaps a box DERIVED from crypt.bounds,
// the surface tints toward pale steam (curling bands live in the ixtint shader module). Own
// owned lane u200-232 (u200=count · slot0 base 201) so tints never contend with the reveal
// lane u150-175. ZERO hardcoded coords — the box is read from the crypt's live field.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms;
if (!V || !Array.isArray(u)) return;
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} });
const cb = IX.fields['crypt.bounds']; // the crypt's declared box (vf-crypt-room)
const lr = IX.fields['lantern.reality']; // the thrown lantern's reality sphere (vf-lantern)
const S = V.__steam || (V.__steam = { expose: 0, cx: 0, cy: 0, cz: 0, cr: 0 });
const sdt = Math.min(dt || 0.016, 1 / 30);
// steam pocket = a cold layer over the crypt floor, inset from the walls. Derived each tick.
let box = null;
if (cb && cb.active !== false && cb.shape === 'box') {
box = { x: cb.x, y: cb.y - cb.hy * 0.35, z: cb.z, hx: cb.hx * 0.9, hy: cb.hy * 0.5, hz: cb.hz * 0.9 };
IX.fields['tomb.steam'] = { node: 'vf-tomb-steam', tag: 'steam-pocket', shape: 'box', active: true,
x: box.x, y: box.y, z: box.z, hx: box.hx, hy: box.hy, hz: box.hz };
IX.reactions['tomb.steam'] = Object.assign(IX.reactions['tomb.steam'] || {},
{ node: 'vf-tomb-steam', whenTag: 'reality', myField: 'tomb.steam', effect: 'tint', region: 'tint' });
} else if (IX.fields['tomb.steam']) { delete IX.fields['tomb.steam']; }
// cover present iff the lantern is deployed AND its sphere AABB-overlaps the pocket
let covered = false;
if (box && lr && lr.active !== false && lr.shape === 'sphere') {
const rr = lr.r || 0;
covered = Math.abs(lr.x - box.x) <= box.hx + rr &&
Math.abs(lr.y - box.y) <= box.hy + rr &&
Math.abs(lr.z - box.z) <= box.hz + rr;
if (covered) { S.cx = lr.x; S.cy = lr.y; S.cz = lr.z; S.cr = rr; } // latch → smooth fade on recall
}
// eased expose so steam swells in / dissipates out, never pops
S.expose += ((covered ? 1 : 0) - S.expose) * Math.min(1, sdt * 4);
// publish to the OWNED lane u200-232 (layout matches the ixtint module)
const live = !!box && S.expose > 0.003 && S.cr > 0;
u[200] = live ? 1 : 0;
if (live) {
const b = 201;
u[b + 0] = S.expose;
u[b + 1] = S.cx; u[b + 2] = S.cy; u[b + 3] = S.cz;
u[b + 4] = S.cr;
u[b + 5] = box.x; u[b + 6] = box.y; u[b + 7] = box.z;
u[b + 8] = box.hx; u[b + 9] = box.hy; u[b + 10] = box.hz;
u[b + 11] = 0.72; u[b + 12] = 0.84; u[b + 13] = 0.98; // pale cold-steam tint
}
} catch (e) {}
})();hook · vf-zone-secret
by claude-code
// vf-zone-secret — RULE ZONES the lantern reveals. Own node. Publishes each secret's
// world pos + reveal to u140-148. The SHADER does the graphics: the nave secret (A)
// opens an actual wall (rooms.wgsl gates its lancet carve on u144); the Room A portal
// (B) is an invisible-portal zone that the lantern SHINE lights up + reveals (u145-148,
// s3 secret block). Held lantern glimmers them at range; a THROWN sphere covering one
// latches it. NO floating orbs. Owns u140-148.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
if (!V.__secrets) V.__secrets = [
{ x: 0.0, y: 2.5, z: 8.8, rev: 0, door: 1 }, // nave dais wall — graphical barrier (u144)
{ x: 0.0, y: 2.0, z: -22.5, rev: 0 } // ROOM A forward portal — the invisible portal zone (where you are)
]
const S = V.__secrets
const dep = u[128] > 0.5, sx = u[129] || 0, sy = u[130] || 0, sz = u[131] || 0, sr = u[132] || 0
u[140] = S.length
for (let i = 0; i < S.length && i < 2; i++) {
const s = S[i]
if (dep && sr > 3.0) {
const d = Math.hypot(s.x - sx, s.z - sz) // floor projection — grounded flame counts (fix: Claude Opus)
if (d < sr * 0.85) {
if (s.rev < 0.02 && wd.__play_sound == null) wd.__play_sound = [{ frequency: 200, duration: 0.26, volume: 0.26, type: 'sawtooth' }, { frequency: 380, duration: 0.36, volume: 0.16, type: 'sine' }]
s.rev = Math.min(1, s.rev + (dt || 0.016) * 1.6)
}
}
const b = 141 + i * 4
u[b] = s.x; u[b + 1] = s.y; u[b + 2] = s.z; u[b + 3] = s.rev
}
}
} catch (e) {}
hook · vf-goto
by claude-code
// vf-goto — THE VISIT CONSUMER: the engine's node-graph ⤷ VISIT button writes
// worldData.__goto {x,y,z,hook,at}; this node moves the player there (warp latch
// PINNED so avenue arrivals never land on the unreal side) and clears it. Fresh
// __goto only (8s window) — a stale one from a snapshot is ignored, never a yank.
try {
const wd = sim.worldData, V = wd.__vf
const g = wd.__goto
if (V && g && typeof g.x === 'number' && typeof g.z === 'number') {
if (g.at && Date.now() - g.at < 8000 && g.__done == null) {
g.__done = 1
V.px = g.x; V.pz = g.z
V.py = Math.max(1.7, (typeof g.y === 'number' && isFinite(g.y) ? g.y : 0) + 1.2)
V.warp = 1 // pin the warren latch — a jump is not a crossing
wd.__play_sound = [{ frequency: 520, duration: 0.12, volume: 0.18, type: 'sine' }, { frequency: 780, duration: 0.16, volume: 0.12, type: 'triangle' }]
} else if (!g.at || Date.now() - g.at > 8000) {
wd.__goto = null // sweep stale
}
}
} catch (e) {}
hook · vf-veil-lurker
by claude-code
// vf-veil-lurker — BORN-DECLARED danger zone #2 (the descent). A creature cysted in
// UNREAL space on the risen-nave avenue. Its den exists only under thrown reality
// (ix region 2 — the organic cyst). When the reveal takes it: the LURKER WAKES,
// bursts out, and CHARGES — and once woken it is REAL and STAYS (recalling the
// lantern un-renders the den, not the creature). Real = mortal: ~3 bolts kill it.
// Coordinates live HERE ONLY. Declares fields fresh every tick (ix stale-sweep).
try {
const wd = sim.worldData, V = wd.__vf
if (V) {
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} })
const DEN = { x: -6.5, y: 1.9, z: -72.0, hx: 3.1, hy: 1.9, hz: 3.1 } // BIG walk-in den
IX.fields['lurker.den'] = { node: 'vf-veil-lurker', tag: 'den-bounds', shape: 'box', active: true,
x: DEN.x, y: DEN.y, z: DEN.z, hx: DEN.hx, hy: DEN.hy, hz: DEN.hz }
const r = IX.reactions['lurker.reveal'] = Object.assign(
IX.reactions['lurker.reveal'] || {},
{ node: 'vf-veil-lurker', whenTag: 'reality', myField: 'lurker.den', effect: 'reveal', region: 2 })
if (!V.__lurker) V.__lurker = { woke: 0, dead: 0, hp: 1, x: DEN.x, z: DEN.z, phase: 0, hitCd: 0, lastBeat: -1 }
const L = V.__lurker
const engineAlive = IX.beat != null && IX.beat !== L.lastBeat
L.lastBeat = IX.beat != null ? IX.beat : L.lastBeat
if (r.justEntered && !L.woke && !L.dead) {
r.justEntered = false // consume the edge
L.woke = 1
if (wd.__play_sound == null) wd.__play_sound = [
{ frequency: 70, duration: 0.55, volume: 0.32, type: 'sawtooth' },
{ frequency: 880, duration: 0.12, volume: 0.18, type: 'square' }]
}
const sdt = Math.min(dt || 0.016, 1 / 30)
if (L.woke && !L.dead) {
const px = V.px || 0, pz = V.pz || 0
// CHARGE — only hunts inside the avenue (its ground)
const dx = px - L.x, dz = pz - L.z, dd = Math.hypot(dx, dz) || 1
L.x += (dx / dd) * 3.4 * sdt; L.z += (dz / dd) * 3.4 * sdt
L.x = Math.max(-8, Math.min(8, L.x)); L.z = Math.max(-94, Math.min(-55, L.z))
L.phase += sdt * 3.0
L.hitCd = Math.max(0, L.hitCd - sdt)
// claws: contact damage with a cooldown
if (dd < 0.9 && L.hitCd <= 0) {
L.hitCd = 0.8
V.hp = Math.max(0, (V.hp != null ? V.hp : 1) - 0.22)
if (wd.__play_sound == null) wd.__play_sound = [{ frequency: 140, duration: 0.12, volume: 0.3, type: 'sawtooth' }]
}
// REAL = MORTAL — player bolts connect (mirrors the ambusher hit-test)
if (Array.isArray(V.bolts)) for (const b of V.bolts) {
if (b.life > 0 && Math.hypot(b.x - L.x, b.z - L.z) < 0.75 && b.y > 0 && b.y < 2.2) {
L.hp = Math.max(0, L.hp - 0.34); b.life = 0; L.hurt = 1
if (L.hp <= 0) {
L.dead = 1; V.score = (V.score || 0) - 100 // killing the revealed costs you
// the LOSS has a voice (Galen): a sour falling sting when the kill costs you
wd.__play_sound = [
{ frequency: 220, duration: 0.5, volume: 0.26, type: 'sawtooth' },
{ frequency: 165, duration: 0.6, volume: 0.2, type: 'sawtooth' },
{ frequency: 98, duration: 0.8, volume: 0.16, type: 'sine' }]
if (wd.__play_sound == null) wd.__play_sound = [
{ frequency: 220, duration: 0.3, volume: 0.26, type: 'sawtooth' },
{ frequency: 1200, duration: 0.1, volume: 0.14, type: 'square' }]
}
}
}
// render: kind-2 ambusher body tracking the player; hurt flash rides aux
if (Array.isArray(V.pop)) {
const yaw = Math.atan2(px - L.x, pz - L.z)
V.pop.push(L.x, 0, L.z, 13, 1, L.phase % 1000, Number.isFinite(yaw) ? yaw : 0, L.hurt ? 1 : 0) // kind 13: the VEIL LURKER model
L.hurt = 0
}
} else if (L.dead && L.deadT == null) {
L.deadT = 0
}
// RE-CYST — the unreal reclaims its creature: 75s after death the lurker
// reforms in the den, hidden again, and the trap re-arms for the trek back.
if (L.dead) {
L.recyst = (L.recyst || 0) + sdt
if (L.recyst > 75) {
V.__lurker = { woke: 0, dead: 0, hp: 1, x: DEN.x, z: DEN.z, phase: 0, hitCd: 0, lastBeat: IX.beat != null ? IX.beat : -1 }
if (IX.reactions['lurker.reveal']) { IX.reactions['lurker.reveal'].on = false; IX.reactions['lurker.reveal'].justEntered = false }
}
}
// ── THE DIMENSION DOOR (Galen): the den is a threshold, not a shell ──
const px2 = V.px || 0, pz2 = V.pz || 0
const inDen = Math.abs(px2 - DEN.x) < DEN.hx && Math.abs(pz2 - DEN.z) < DEN.hz
const rOn = !!r.on && engineAlive
if (L.__wasOn == null) L.__wasOn = rOn
if (!V.__ldim) V.__ldim = { inside: false, dwell: 0 }
const LD = V.__ldim
if (!LD.inside) {
// ENTER THE DEN = PORTAL IMMEDIATELY into the big interior (bigger inside
// than out — its own space). You can still walk out via its mouth.
if (inDen && rOn && !LD.inInterior) {
LD.inInterior = true
V.px = 0; V.pz = 78.4; V.py = 1.7; V.yaw = 0; wd.__tpMorph = (wd.__tpMorph || 0) + 1
wd.__play_sound = [{ frequency: 320, duration: 0.2, volume: 0.2, type: 'sine' }, { frequency: 160, duration: 0.3, volume: 0.18, type: 'sawtooth' }]
}
if (LD.inInterior) {
// WALK OUT — cross back through the interior mouth (south wall)
if (pz2 < 77.6 && pz2 > 70) {
LD.inInterior = false
V.px = DEN.x + DEN.hx + 1.0; V.pz = DEN.z; V.py = 1.7; V.warp = 1; wd.__tpMorph = (wd.__tpMorph || 0) + 1 // PIN the warren latch — teleports must not read as a B-side crossing
wd.__play_sound = [{ frequency: 520, duration: 0.14, volume: 0.16, type: 'sine' }]
}
// THE SUMMIT ORB (Galen) — no auto-yank: an orb floats at the column peak.
// SHOOT it from the stairtop → a PORTAL reveals ELSEWHERE (the far floor).
// Step through the portal → the dimension.
if (!LD.orbShot) {
if (Array.isArray(V.pop)) {
V.pop.push(1.3, 4.5 + 0.1 * Math.sin((V.t || 0) * 2.2), 82.6, 12, 0.95, 0, 0, 0) // the orb — floats BESIDE the summit, clear of the column
V.pop.push(1.3, 4.75, 82.6, 5, 0.3 + 0.2 * Math.sin((V.t || 0) * 4), 0, 0, 0) // ember halo
}
if (Array.isArray(V.bolts)) for (const b of V.bolts) {
if (b.life > 0 && Math.hypot(b.x - 1.3, (b.y != null ? b.y : 1) - 4.5, b.z - 82.6) < 0.95) {
b.life = 0; LD.orbShot = 1
wd.__play_sound = [{ frequency: 1400, duration: 0.25, volume: 0.22, type: 'triangle' }, { frequency: 350, duration: 0.55, volume: 0.2, type: 'sine' }]
}
}
// CRYSTAL SOLVE (Galen): hover the thrown crystal-flame over the orb —
// DING + a crystal drops at the column base. The quiet answer; shooting stays.
const LAN2 = V.lantern
if (LAN2 && LAN2.deployed && Math.hypot((LAN2.x || 0) - 1.3, (LAN2.y || 0) - 4.5, (LAN2.z || 0) - 82.6) < 1.25) {
LD.orbShot = 1
LD.crystalSolve = 1
LD.crystalDrop = { x: 1.3, y: 0.35, z: 82.6, t: 0 }
wd.__play_sound = [
{ frequency: 1760, duration: 0.18, volume: 0.22, type: 'sine' },
{ frequency: 2637, duration: 0.3, volume: 0.14, type: 'sine' }] // the DING
}
} else {
// BLUE CONFIRMATION (Galen): after the CRYSTAL solve the orb does not
// vanish — it stays at the summit, turned BLUE, telling you it is answered
if (LD.crystalSolve && Array.isArray(V.pop)) {
V.pop.push(1.3, 4.5 + 0.1 * Math.sin((V.t || 0) * 2.2), 82.6, 3.3, 0.95, 0, 0, 0)
}
// the PORTAL — revealed across the chamber, on the far floor
const PPX = 3.6, PPZ = 85.0
if (globalThis.__VF_PORTAL) globalThis.__VF_PORTAL(V, PPX, PPZ, V.t || 0)
if (Math.hypot(px2 - PPX, pz2 - PPZ) < 0.95) {
LD.inInterior = false; LD.inside = true; LD.orbShot = 0
V.px = 0; V.pz = 60.0; V.py = 1.7; wd.__tpMorph = (wd.__tpMorph || 0) + 1
wd.__play_sound = [{ frequency: 210, duration: 0.5, volume: 0.24, type: 'sine' }, { frequency: 630, duration: 0.4, volume: 0.16, type: 'triangle' }, { frequency: 1260, duration: 0.3, volume: 0.1, type: 'sine' }]
}
}
// the dropped crystal — glints at the column base until collected
if (LD.crystalDrop) {
const DR = LD.crystalDrop; DR.t = (DR.t || 0) + sdt
if (Array.isArray(V.pop)) {
V.pop.push(DR.x, DR.y + 0.15 + 0.06 * Math.sin(DR.t * 3), DR.z, 3.3, 0.85, 0, 0, 0) // BLUE — the pair confirms
V.pop.push(DR.x, DR.y + 0.45, DR.z, 7, 0.25 + 0.15 * Math.sin(DR.t * 5), 0, 0, 0)
}
if (Math.hypot(px2 - DR.x, pz2 - DR.z) < 0.8) {
LD.crystalDrop = null; V.__denCrystal = 1; V.score = (V.score || 0) + 150
wd.__play_sound = [
{ frequency: 1046, duration: 0.12, volume: 0.2, type: 'sine' },
{ frequency: 1568, duration: 0.2, volume: 0.16, type: 'triangle' }]
}
}
}
// THE TRAP — recall the crystal while inside the den and the unreal CLOSES
// OVER you: you are snatched into the lurker's dimension.
if (inDen && L.__wasOn && !rOn) {
LD.inside = true
V.px = 0; V.pz = 64.0; V.py = 1.7; wd.__tpMorph = (wd.__tpMorph || 0) + 1
wd.__play_sound = [{ frequency: 55, duration: 0.8, volume: 0.34, type: 'sawtooth' }, { frequency: 110, duration: 0.6, volume: 0.2, type: 'sawtooth' }]
}
} else {
// INSIDE THE DIMENSION — a pale return-glyph floats at the far end; step on it
if (Array.isArray(V.pop)) {
if (globalThis.__VF_PORTAL) globalThis.__VF_PORTAL(V, 0, 66.5, V.t || 0)
}
if (Math.hypot(px2 - 0, pz2 - 66.5) < 0.9) {
LD.inside = false
V.px = DEN.x + DEN.hx + 0.8; V.pz = DEN.z; V.py = 1.7; V.warp = 1; wd.__tpMorph = (wd.__tpMorph || 0) + 1 // back at the den mouth (latch pinned)
wd.__play_sound = [{ frequency: 880, duration: 0.2, volume: 0.2, type: 'sine' }, { frequency: 440, duration: 0.3, volume: 0.14, type: 'triangle' }]
}
// fell out of the pocket somehow → clear the flag
if (pz2 < 50) LD.inside = false
}
L.__wasOn = rOn
if (L.deadT != null && L.deadT < 1.2 && Array.isArray(V.pop)) { // death embers
L.deadT += sdt
for (let i = 0; i < 5; i++) { const a = i * 1.256 + L.deadT * 4
V.pop.push(L.x + Math.cos(a) * L.deadT * 2, 0.4 + L.deadT * 1.5, L.z + Math.sin(a) * L.deadT * 2, 5, 0.8 * (1.2 - L.deadT), 0, 0, 0) }
}
}
} catch (e) {}
hook · vf-frame
by claude-code
;(() => {
// vf-frame — WORLD FRAME: per-tick init + the shared ROOM GEOMETRY library, built ONCE per worker
// and published as the worker-global __VF_GEO (functions must NEVER enter worldData — DataCloneError
// kills the sandbox sync). Consumers destructure __VF_GEO inside their own build-once cache.
const wd = sim.worldData
wd.__mouseLook = true
if (!wd.__vf || wd.__vf.ver !== 1) wd.__vf = { ver: 1 }
if (!Array.isArray(wd.gpuUniforms) || wd.gpuUniforms.length < 256) { wd.gpuUniforms = new Array(256).fill(0) }
wd.__vf.pop = []
// ── UNIFIED PORTAL GRAPHIC (Galen): every portal in veilfire draws through this —
// a STANDING OVAL RING of portal-crystal (kind 12) with a plasma-blue shimmer
// core (kind 3.3), slowly spinning. One shape for all gateways (was: ad-hoc gold
// orb + crystal stacks that read as a "golden mushroom"). Cheap (~21 pop points). ──
if (!globalThis.__VF_PORTAL) globalThis.__VF_PORTAL = function (V, x, z, t) {
if (!V || !Array.isArray(V.pop)) return
const spin = (t || 0) * 1.4
for (let i = 0; i < 16; i++) {
const a = (i / 16) * 6.28318 + spin
const rr = 1.0 + 0.06 * Math.sin(a * 3 - spin * 2)
V.pop.push(x + Math.cos(a) * rr, 0.12 + (Math.sin(a) * 0.5 + 0.5) * 2.3, z, 12, 0.65 + 0.35 * Math.sin(a * 2 + spin), 0, 0, 0)
}
for (let i = 0; i < 5; i++) { const a = spin * 2.2 + i * 1.2566
V.pop.push(x + Math.cos(a) * (0.32 + 0.25 * Math.sin(spin + i)), 1.15 + Math.sin(a) * 0.5, z, 3.3, 0.45, 0, 0, 0) }
}
try { const _s = wd.__vf.__secrets; globalThis.__VF_SECRET0 = (_s && _s[0] ? (_s[0].rev || 0) : 0) } catch (e) {}
wd.__vf.t = (wd.__vf.t || 0) + Math.min(dt, 1 / 30)
if (globalThis.__VF_GEO_REV !== '63dd2790e2') {
// GENERATED by swarm/generate-lair-maze.mjs (seed 1337) — DO NOT hand-edit; regenerate.
// Collision mirror of veilfire/maze.wgsl mod_maze — ONE SOURCE. mazeBlocked(x,z,R)
// = true if a body of radius R at (x,z) overlaps a maze wall/post (lair-avenue only).
const MAZE = {
NX: 4, NZ: 14, C: 2.675, X0: -5.35, Z0: -56, WH: 0.17, PR: 0.36,
Xhi: 5.35, Zlo: -93.44999999999999,
V: [3623993343, 4278307250, 63], H: [1861831249, 39926629],
entry: { i: 1, x: -1.34, z: -57.3375 },
deadEnds: [{"i":0,"j":13,"x":-4.01,"z":-92.11},{"i":1,"j":5,"x":-1.34,"z":-70.71},{"i":1,"j":11,"x":-1.34,"z":-86.76},{"i":3,"j":8,"x":4.01,"z":-78.74}],
path: [[1,0],[0,0],[0,1],[0,2],[0,3],[1,3],[1,2],[1,1],[2,1],[2,0],[3,0],[3,1],[3,2],[2,2],[2,3],[3,3],[3,4],[3,5],[3,6],[3,7],[2,7],[1,7],[0,7],[0,8],[1,8],[1,9],[0,9],[0,10],[1,10],[2,10],[3,10],[3,11],[2,11],[2,12],[3,12],[3,13]],
}
const vOn = (i, j) => (i < 0 || i > MAZE.NX || j < 0 || j >= MAZE.NZ) ? true : ((MAZE.V[((i * MAZE.NZ + j) / 32) | 0] >> (((i * MAZE.NZ + j) % 32))) & 1) === 1
const hOn = (i, j) => (i < 0 || i >= MAZE.NX || j < 0 || j > MAZE.NZ) ? true : ((MAZE.H[((i * (MAZE.NZ + 1) + j) / 32) | 0] >> (((i * (MAZE.NZ + 1) + j) % 32))) & 1) === 1
function segD(px, pz, ax, az, halfx, halfz) { const dx = Math.abs(px - ax) - halfx, dz = Math.abs(pz - az) - halfz; const ox = Math.max(dx, 0), oz = Math.max(dz, 0); return Math.hypot(ox, oz) + Math.min(Math.max(dx, dz), 0) }
function mazeBlocked(x, z, R) {
const m = R + Math.max(MAZE.WH, MAZE.PR) // boundary walls/posts extend past the region edge — early-out must clear the widest (posts)
if (x < MAZE.X0 - m || x > MAZE.Xhi + m || z > MAZE.Z0 + m || z < MAZE.Zlo - m) return false
const fi = (x - MAZE.X0) / MAZE.C, fj = (MAZE.Z0 - z) / MAZE.C
const i = Math.max(0, Math.min(MAZE.NX - 1, Math.floor(fi))), j = Math.max(0, Math.min(MAZE.NZ - 1, Math.floor(fj)))
let d = 1e5
if (vOn(i, j)) d = Math.min(d, segD(x, z, MAZE.X0 + i * MAZE.C, MAZE.Z0 - (j + 0.5) * MAZE.C, MAZE.WH, MAZE.C * 0.5))
if (vOn(i + 1, j)) d = Math.min(d, segD(x, z, MAZE.X0 + (i + 1) * MAZE.C, MAZE.Z0 - (j + 0.5) * MAZE.C, MAZE.WH, MAZE.C * 0.5))
if (hOn(i, j)) d = Math.min(d, segD(x, z, MAZE.X0 + (i + 0.5) * MAZE.C, MAZE.Z0 - j * MAZE.C, MAZE.C * 0.5, MAZE.WH))
if (hOn(i, j + 1)) d = Math.min(d, segD(x, z, MAZE.X0 + (i + 0.5) * MAZE.C, MAZE.Z0 - (j + 1) * MAZE.C, MAZE.C * 0.5, MAZE.WH))
for (let ci = 0; ci <= 1; ci++) for (let cj = 0; cj <= 1; cj++) {
const cx = MAZE.X0 + (i + ci) * MAZE.C, cz = MAZE.Z0 - (j + cj) * MAZE.C
d = Math.min(d, Math.hypot(x - cx, z - cz) - MAZE.PR)
}
return d < R
}
// movement (node: movement) — v2. WASD + mouse-look + jump. A step-hook FRAGMENT
// run FIRST each frame. Owns player + camera on the whiteboard:
// rows 1,2,3 = x,y,z · row 4 = yaw · row 5 = pitch · uni4(60)=ro,fov · uni4(61)=target
//
// v2 controls (standard FPS): MOUSE looks (yaw+pitch by pointer delta), W/S walk,
// A/D STRAFE, SPACE jumps (gravity + ground). Fire moved to click (projectiles).
// Collision: coarse walkable mask mirroring veilfire/rooms.wgsl extents, wall-slid.
// (KNOWN: columns/dais aren't blockers yet — the clip fix is a rooms+movement mod.)
const R = 0.4
// WARREN + nave collision — mirrors veilfire/rooms.wgsl EXACTLY (one truth, two
// callers). Extents authored in swarm/warren-extents.json; where that JSON and
// the shader disagree, the SHADER wins (it is what renders). Layout:
// · nave colonnade — two rows x=±3.2, cols every 4 in z (z=0,±4,±8), r=0.4,
// drawn only z>-8.8.
// · nave -z WALL slab z∈[-9.8,-9.0] spanning x∈[-4,4], pierced ONLY by the
// grand lancet arch x∈[-2.8,2.8] — the sole way into the warren.
// · the COLUMN: floor→ceiling cylinder at (x=0, z=-12.5) r=0.5, always blocks
// (hides the room seam). This is the commit plane.
// · beyond the column (z<-12.5) TWO rooms differ (the impossibility):
// Room A (committed warp>=0) warm colonnade, x∈[-4,4] z∈[-24,-12.5] —
// pillar rows x=±2.4 r=0.35, z-repeat = (fract((z+1.5)/3)-0.5)*3
// (centres z=-15,-18,-21,-24) — matches the shader.
// Room B (committed warp<0) cool GRAND VAULT, x∈[-4,4] z∈[-30,-12.5]
// (DEEPER than A) — a far-end SHRINE at z=-27: stone pedestal (footprint
// |x|,|z+27| ≤ 1.0) + the glowing altar-key sphere (0,2.6,-27) r=1.1.
// Dais step-up (floorH) is a separate movement feature, unchanged.
// warp = which warren room the player has committed to:
// 0 none · +1 Room A · −1 Room B · +2 THE LAIR (shares Room A's volume + collision).
// CORRIDOR (Room A / lair only, past the far wall z=-24): a tube to a reward
// chamber whose LENGTH is direction-latched (short ~10u IN, long ~28u BACK). The
// player z is remapped at the fold so this collision + rooms.wgsl always agree.
// The chamber mouth (z=-56) now OPENS onto THE RISEN NAVE — a grand grown-Gothic
// avenue (|x|≤~8.4 walkable between the |x|=9 colonnades, to the gable at z≈-95).
const WCOLZ = -12.5, WCOLR = 0.5
const CORR_WALLZ = -24, CORR_LEN_IN = 10, CORR_LEN_BACK = 28
const CORR_HW = 1.0, CHAMB_HW = 2.5, CHAMB_D = 4.0
// THE RISEN NAVE — the grand avenue past the corridor (mirrors rooms.wgsl + the
// grown cathedral coords in veilfire/cathedral.wgsl EXACTLY). The reward chamber
// mouth (z=-56) OPENS into it at z=-54; the colonnade walls at world |x|=9 block,
// the crow-step gable facade (z≈-95) closes the far end. Growth (uni48) is visual
// only — collision is the fully-grown footprint.
const RISEN_Z0 = -54 // avenue near face (chamber opens here)
const RISEN_GABLE_Z = -95 // gable facade wall front (player stops before it)
const RISEN_ARCADE_X = 5.75 // colonnade inner face at world |x|=6 (narrowed from 9; half-thick ~0.25)
// WEAVE-THROUGH FOREST — mirrors cathedral.wgsl mod_cath_grove EXACTLY: staggered
// piers every 4u in z from CATH_GROVE_Z0, even rows x∈{-4,0,4}, odd rows x∈{-2,2},
// r≈0.5. Full-grown footprint (growth is visual only, like the maze) — safe because
// the shader reveal is full ~4u out, well before this ~0.9u contact, so a pier is
// always visible before it can block you. Only in the cathedral (warp≤1.5).
const GROVE_Z0 = -62
function cathGroveBlocked(x, z) {
const k = Math.round((GROVE_Z0 - z) / 4)
if (k < 0 || k > 8) return false
const zr = GROVE_Z0 - 4 * k
const rC = 0.5 + R
const xs = (k % 2 === 0) ? [-4, 0, 4] : [-2, 2]
for (const cx of xs)
if ((x - cx) * (x - cx) + (z - zr) * (z - zr) < rC * rC) return true
return false
}
function inAvenue(x, z, warp) {
if (z < RISEN_GABLE_Z + R && warp > 1.5) { // THE OCTAGON ARENA — past the gable, lair only
if (z > -99 && Math.abs(x - 4.012) < 1.7 - R) return true // tunnel: exit door → arena
const ax = x - 4.012, az = z + 106
if (Math.max(Math.max(Math.abs(ax), Math.abs(az)), (Math.abs(ax) + Math.abs(az)) * 0.70710678) < 9.0 - R) return true // inside the octagon floor (matches the visual apothem 9)
return false
}
if (z < RISEN_GABLE_Z + R) return false // gable facade — the far wall
if (Math.abs(x) > RISEN_ARCADE_X - R) return false // colonnade walls at |x|=9
// (labyrinth deleted — the hall is open; no maze-wall collision)
if (warp <= 1.5 && cathGroveBlocked(x, z)) return false // THE CATHEDRAL — weave-through pier forest (mirrors mod_cath_grove)
return true
}
// walkable region past the Room-A door (z < -24): a STRAIGHT open corridor
// (x∈[-1.6,1.6]) from the door to the avenue near face (z=-54), then the avenue.
// Continuous — the door opens onto the visible growing cathedral. (Old fold/
// chamber/reward-orb removed: it made the door a dead-end wall.)
function inCorridor(x, z, L, warp) {
if (z <= RISEN_Z0) return inAvenue(x, z, warp) // opens onto THE RISEN NAVE (a maze in the lair)
return Math.abs(x) <= 2.6 - R // straight approach tube (walkable |x|<=2.2, matching the widened visible opening)
}
function blocked(x, z, warp) {
if (z > -8.8) { // --- nave: colonnade ---
const cr = 0.4 + R
for (const cx of [-3.2, 3.2])
for (const cz of [-8, -4, 0, 4, 8])
if ((x - cx) * (x - cx) + (z - cz) * (z - cz) < cr * cr) return true
return false
}
// --- warren (z <= -8.8) ---
// grand column always blocks (floor→ceiling), at (0, -12.5) r=0.5
if (x * x + (z - WCOLZ) * (z - WCOLZ) < (WCOLR + R) * (WCOLR + R)) return true
// committed-room contents, only beyond the column plane
if (z < WCOLZ) {
if (warp < 0) { // Room B — far-end SHRINE at (0,-27)
if (Math.abs(x) < 1.0 + R && Math.abs(z + 27) < 1.0 + R) return true // pedestal footprint (visible; stops you close enough to grab the key)
// NB: the floating altar-key sphere is NOT a collider — it hovers above the
// pedestal and vanishes on pickup, so nothing invisible ever blocks you.
} else if (z > -22.5) { // Room A — pillar rows x=±2.4 r=0.35 (none at the door mouth z<=-22.5; mirrors rooms.wgsl)
const f = (z + 1.5) / 3
const pz = ((f - Math.floor(f)) - 0.5) * 3 // shader fract repeat
const pr = 0.35 + R
if ((Math.abs(x) - 2.4) ** 2 + pz * pz < pr * pr) return true
}
}
return false
}
function walkable(x, z, warp, L, doorOpen) {
// ACT II CITY (Galen) — checked FIRST so the avenue/gable rejection below can't
// shadow it. Once the relics combine, the approach tunnel + the domed city streets
// (between the procedural towers) are walkable; towers block. Mirrors rooms.wgsl.
if ((globalThis.__VF_RELICS_ALL || 0) > 0.5 && z < -87 && warp < 1.5) { // CATHEDRAL reality only (warp param is wing-aware) — the city floor never exists in the dragon wing
const appr = Math.abs(x) < 3.2 - R && z >= -99.5 && z <= -87
const inDome = (x * x + (z + 118) * (z + 118)) < (20 - R) * (20 - R)
let street = false
if (inDome) {
const gx = (((x / 7) % 1 + 1) % 1 - 0.5) * 7, gz = (((z / 7) % 1 + 1) % 1 - 0.5) * 7
street = !(Math.abs(gx) < 1.8 + R && Math.abs(gz) < 1.8 + R)
}
if (appr || (inDome && street)) return true
}
// Room B (warp<0): the DEEP grand vault z∈[-30,-12.5], full width, NO corridor.
if (warp < 0) {
if (z <= -9.0 + R && z >= -9.8 - R && Math.abs(x) > 2.8 - R) return false // nave -z wall / arch
const inB = x >= -4 + R && x <= 4 - R && z >= -30 + R && z <= 9 - R
return inB && !blocked(x, z, warp)
}
// Room A / lair / nave (warp>=0): corridor system beyond the Room-A far wall,
// only once the lock is open; length L is direction-latched by the caller.
if (z < -24 + R) {
if (warp < 0.5 || !doorOpen) return false
return inCorridor(x, z, L, warp)
}
// nave -z wall: solid slab z∈[-9.8,-9.0] except the arch x∈[-2.8,2.8]; with the
// player radius the solid part (and its jambs) is impassable.
if (z <= -9.0 + R && z >= -9.8 - R && Math.abs(x) > 2.8 - R) return false
const main = x >= -4 + R && x <= 4 - R && z >= -24 + R && z <= 9 - R // nave + Room A footprint
const side = x >= 5 + R && x <= 11 - R && z >= -3.5 + R && z <= 3.5 - R
const door = x >= 3.3 && x <= 5.7 && z >= -1.5 + R && z <= 1.5 - R
const secret = (x >= -4 + R && x <= 4 - R && z >= 9 - R && z <= 17.5 - R) && ((globalThis.__VF_SECRET0 || 0) > 0.5) // SECRET ROOM — sealed until the lantern reveal opens the graphical barrier span passable (Galen: walk through the back wall)
const sarcoMouth = ((wd.gpuUniforms && wd.gpuUniforms[177] || 0) > 0.8) && Math.abs(x - (-0.767)) < 0.72 && Math.abs(z - (-5.05)) < 1.3 // SARCO MOUTH — stand on the open lid to drop in
const crypt = ((globalThis.__VF_CRYPT0 || 0) > 0.5) && (
(x >= -6.4 && x <= -3.6 && z >= -5.72 && z <= -4.28) ||
(x >= -10.4 + R && x <= -6.2 && z >= -7.1 + R && z <= -2.9 - R &&
!(Math.abs(x + 8.4) < 0.72 + R && Math.abs(z + 5.0) < 1.3 + R))) // THE CRYPT side-wing (west wall) — walkable only while its ix reveal holds; sarcophagus solid
const hall = (x >= 10.5 && x <= 18 - R && z >= -1.0 + R && z <= 1.0 - R) // PERMANENT HALLWAY (Galen) — side chamber +x wall opened into the escape hall
const woven = (x >= 18 - R && x <= 26.5 - R && z >= -3.0 + R && z <= 3.0 - R) // THE WOVEN ROOM at the hall end (Galen)
const vch = (x >= -4 + R && x <= 4 - R && z >= 36 + R && z <= 44 - R) // VOID CHAMBER (Galen) — the portal room, walkable
const ldim = (x >= -5 + R && x <= 5 - R && z >= 56.5 + R && z <= 67.5 - R) // THE LURKER DIMENSION pocket
const tomb = (x >= -4.6 + R && x <= 4.6 - R && z >= 92 + R && z <= 102 - R) // THE TOMB DIMENSION
const denInt = (Math.abs(x) <= 5.6 - R && z >= 76.9 + R && z <= 87.2 - R && Math.hypot(x, z - 82) > 0.95 + R) // THE DEN INTERIOR (big inside), column solid
return ((main || side || door) && !blocked(x, z, warp)) || secret || hall || vch || woven || ldim || denInt || sarcoMouth || tomb || crypt
}
// floor height per position — the dais step at the far +z end of the nave
// (rooms.wgsl: x∈[-3.5,3.5] z∈[6,9], raised +1). Lets you step UP instead of
// clipping the back wall near the altar/bench.
function floorH(x, z) {
if (x >= -3.5 && x <= 3.5 && z >= 6.0 && z <= 9.0) return 1.0
// SARCO MOUTH FLOOR: while the lid is open (u177>0.8) the low box rim is a 0.14
// step you walk onto — then vf-crypt-room drops you in. No jump needed.
if ((wd.gpuUniforms && wd.gpuUniforms[177] || 0) > 0.8 && Math.abs(x - (-0.767)) < 0.72 && Math.abs(z - (-5.05)) < 1.3) return 0.14
// DEN INTERIOR spiral (the big inside): 12 REAL steps helixing the column —
// mirrors rooms.wgsl diStep EXACTLY so you climb what you see.
const ddx = x, ddz = z - 82
const rr2 = Math.hypot(ddx, ddz)
if (z > 76.9 && z < 87.2 && Math.abs(x) < 5.7 && rr2 > 1.05 && rr2 < 3.4) {
let ang = Math.atan2(ddx, -ddz)
if (ang < 0) ang += Math.PI * 2
const k = Math.min(11, Math.floor(ang / (Math.PI * 2) * 12))
return k * 0.30 + 0.24
}
return 0.0
}
globalThis.__VF_GEO = { MAZE, vOn, hOn, segD, mazeBlocked, R, WCOLZ, WCOLR, CORR_WALLZ, CORR_LEN_IN, CORR_LEN_BACK, CORR_HW, CHAMB_HW, CHAMB_D, RISEN_Z0, RISEN_GABLE_Z, RISEN_ARCADE_X, GROVE_Z0, cathGroveBlocked, inAvenue, inCorridor, blocked, walkable, floorH }
globalThis.__VF_GEO_REV = '63dd2790e0'
globalThis.__VF_FNS = {} // geo changed → every cached subsystem closure is stale
}
if (wd.__vfGeo) delete wd.__vfGeo // functions in worldData DataCloneError the worker sync — never again
})()hook · vf-crypt-room
crypt wing + sarcophagus lid/drop (free tomb exit removed, Aug 20)
// vf-crypt-room v3 — THE CRYPT as a true SIDE ROOM (Galen: "move that room into the
// side room after the hallway, no sarcophagus in nave — rebuild from scratch, design
// concepts forward"). Everything learned, applied:
// · PLACEMENT: an east-west wing OFF the nave's west wall (x < -4). Hallway east
// third → chamber west two-thirds. ZERO geometry on the nave floor.
// · RENDER: ix region 3 (ix_crypt_geo, coordinate-free from the declared box) +
// mod_ix_door carves the wall doorway only while the reveal holds.
// · COLLISION: the HOUSE pattern — a walkable pocket in vf-frame gated on
// globalThis.__VF_CRYPT0 (set here). Real solid walls, sarcophagus solid,
// no per-node push-out hacks.
// · LOAD/UNLOAD: purely lantern-driven — throw covers it → loads; reclaim → unloads
// (inside or out). No latched seal.
// · SARCOPHAGUS SPACE (Galen, pending spec): jump-on-top / shoot-open the lid →
// load sarcophagus space. Not built until the destination is confirmed.
// The WING const is the ONLY place these coordinates live — nothing in any shader.
try {
const wd = sim.worldData, V = wd.__vf
if (V) {
const IX = globalThis.__IX || (globalThis.__IX = { fields: {}, reactions: {} })
const WING = { x: -7.3, y: 1.6, z: -5.0, hx: 3.3, hy: 1.6, hz: 2.4 }
IX.fields['crypt.bounds'] = {
node: 'vf-crypt-room', tag: 'room-bounds', shape: 'box', active: true,
x: WING.x, y: WING.y, z: WING.z, hx: WING.hx, hy: WING.hy, hz: WING.hz
}
const r = IX.reactions['crypt.reveal'] = Object.assign(
IX.reactions['crypt.reveal'] || {},
{ node: 'vf-crypt-room', whenTag: 'reality', myField: 'crypt.bounds', effect: 'reveal', region: 3 })
if (IX.fields['crypt.seal']) delete IX.fields['crypt.seal'] // retire any v2 leftover
if (!V.__crypt) V.__crypt = { woke: 0, stung: 0, lastBeat: -1 }
const C = V.__crypt
const engineAlive = IX.beat != null && IX.beat !== C.lastBeat
C.lastBeat = IX.beat != null ? IX.beat : C.lastBeat
const on = !!r.on && engineAlive
// the walkable pocket follows the reveal (vf-frame reads this flag)
globalThis.__VF_CRYPT0 = on ? 1 : 0
if (!on) { C.woke = 0; C.stung = 0 }
if (on && r.justEntered) { r.justEntered = false; C.woke = 1 }
if (C.woke && !C.stung && wd.__play_sound == null) {
C.stung = 1
wd.__play_sound = [
{ frequency: 70, duration: 1.1, volume: 0.26, type: 'sine' },
{ frequency: 138, duration: 0.9, volume: 0.14, type: 'triangle' }]
}
if (on && Array.isArray(V.pop)) {
const t = V.t || 0
const chx = WING.x - WING.hx / 3 // chamber heart (matches the render)
for (let i = 0; i < 2; i++) {
const sz = (i ? 1 : -1) * 1.55
V.pop.push(chx, 0.95, WING.z + sz, 7, 0.36 + 0.06 * Math.sin(t * 6 + i * 2.1), 0, 0, 0)
}
// THE THIRD CRYSTAL (Galen: "should be in the sarcophagus room") — a blue
// crystal resting on the sarcophagus lid while the crypt is revealed; walk
// over it to collect (once). Matches the den drop: +150, latches __cryptCrystal.
if (!V.__cryptCrystal) {
const cy = 1.35 + 0.08 * Math.sin(t * 2.4)
V.pop.push(chx, cy, WING.z, 3.3, 0.9, 0, 0, 0) // BLUE crystal
V.pop.push(chx, cy + 0.35, WING.z, 7, 0.24 + 0.14 * Math.sin(t * 5), 0, 0, 0) // ember halo
const px = V.px || 0, pz = V.pz || 0
if (Math.hypot(px - chx, pz - WING.z) < 0.9) {
V.__cryptCrystal = 1; V.score = (V.score || 0) + 150
wd.__play_sound = [
{ frequency: 1046, duration: 0.12, volume: 0.2, type: 'sine' },
{ frequency: 1568, duration: 0.22, volume: 0.16, type: 'triangle' },
{ frequency: 2093, duration: 0.3, volume: 0.1, type: 'sine' }]
}
}
}
}
} catch (e) {}
// ── SARCOPHAGUS LID + TOMB DIMENSION (Galen, added under fresh claim): click the
// lid to grind it open; the open sarcophagus is a void mouth — step in to DROP
// into THE TOMB DIMENSION (pocket z 92-102). Return glyph brings you back.
// Publishes lid state on u177 (ix crypt geometry slides the lid by it). ──
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
// READ the crypt-owner's LIVE wing from the shared __IX registry so the
// sarcophagus coords can never drift from theirs again (was hardcoded x:0 while
// their wing moved to x:-7.3 — the "cannot enter" bug the playthrough missed
// because the harness ran my stale constant, not the live geometry).
const _cb = (globalThis.__IX && globalThis.__IX.fields && globalThis.__IX.fields['crypt.bounds']) || { x: -7.3, z: -5.0, hx: 3.3 }
const scx = _cb.x - _cb.hx / 3.0, scy = 0.5, scz = _cb.z // sarcophagus center = chamber heart
if (!V.__sarco) V.__sarco = { open: 0, edge: false, inTomb: false }
const S = V.__sarco
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
const yaw = V.yaw || 0, pitch = V.pitch || 0
const sdt2 = Math.min(dt || 0.016, 1 / 30)
// USE THE CRYSTAL ON IT (Galen): the thrown lantern reality-sphere covering the
// sarcophagus grinds the lid open — reality unseals the tomb.
const L = V.lantern
// SELF-HEAL (Galen soft-lock: dying/reloading in the tomb left __sarco latched
// {open:1,inTomb:true} so the portal-in could never re-open). If we think we're in
// the tomb OR the lid is open but the player is NOT at the sarcophagus and NOT in
// the tomb (pz<90), the state is stale → reseal so re-entry always works again.
// heal the soft-lock latch: flagged in-tomb but not at tomb pos → clear
if (S.inTomb && pz < 90) { S.inTomb = false; S.swT = 0 }
// heal a STUCK lid ONLY when nothing is actively opening it (died/reloaded with the
// lid ajar) — NEVER fight the legit grind: the lid opens by the lantern COVERING the
// sarcophagus (from across the room), not by the player standing on it.
const _lanOn = L && L.deployed && Math.hypot((L.x || 0) - scx, (L.y || 0) - 0.7, (L.z || 0) - scz) < (L.rad || 0) * 0.9 + 0.8
const _nearS = Math.abs(px - scx) < 2.5 && Math.abs(pz - scz) < 3.0
if (S.open > 0.02 && pz < 90 && !_lanOn && !_nearS) { S.open = 0; S.edge = false }
if (S.open < 0.02 && L && L.deployed) {
const dd = Math.hypot((L.x || 0) - scx, (L.y || 0) - 0.7, (L.z || 0) - scz)
if (dd < (L.rad || 0) * 0.9 + 0.8) {
S.open = 0.03
wd.__play_sound = [{ frequency: 60, duration: 0.9, volume: 0.34, type: 'sawtooth' }, { frequency: 240, duration: 0.5, volume: 0.16, type: 'square' }]
}
}
if (S.open > 0 && S.open < 1) S.open = Math.min(1, S.open + sdt2 * 1.8) // the grind
u[177] = S.open
// THE SWALLOW (Galen): don't make the player climb or jump onto anything — the
// instant the lid is open and they walk UP TO the sarcophagus, physicality is
// dropped and the void takes them. Proximity, not standing-on-a-surface, so no
// fake wall to fight. A short pull-in, then the drop.
if (!S.inTomb && S.open > 0.35) {
const dxs = px - scx, dzs = pz - scz
if (Math.abs(dxs) < 1.65 && Math.abs(dzs) < 2.2) {
S.swT = (S.swT || 0) + sdt2
// PULL IN — reel the player toward the mouth center (overrides any wall)
V.px = px + (scx - px) * Math.min(1, sdt2 * 6)
V.pz = pz + (scz - pz) * Math.min(1, sdt2 * 6)
if (Array.isArray(V.pop)) for (let i = 0; i < 4; i++) { const a = (V.t||0)*5 + i*1.57
V.pop.push(scx + Math.cos(a) * (0.6 - S.swT*0.4), 1.2 - S.swT, scz + Math.sin(a) * (0.6 - S.swT*0.4), 18, 0.6, 0, 0, 0) }
if (S.swT > 0.35) { // then DROP into the tomb
S.inTomb = true; S.swT = 0
V.px = 0; V.pz = 93.0; V.py = 1.7; V.warp = 1; V.vy = 0 // ENTRY LEDGE — clear of the watcher orbit (safe drop)
// RECALL the lantern across the dimension jump — it was deployed back in the
// crypt (that's what opened the lid). Left deployed, vf-tomb lights the UNLIGHT
// sphere at the crypt's coords (~100u from the tomb) while you stand here — a
// stale cross-dimension shader state (top hard-freeze suspect). You arrive
// lantern-in-hand and re-throw it here (the tomb's own unmake-the-floor mechanic).
if (V.lantern) { V.lantern.deployed = false; V.lantern.rad = 0 }
if (Array.isArray(u)) { u[178] = 0; u[128] = 0; u[132] = 0 }
wd.__play_sound = [{ frequency: 42, duration: 1.1, volume: 0.36, type: 'sawtooth' }, { frequency: 90, duration: 0.7, volume: 0.2, type: 'sine' }]
}
} else { S.swT = 0 }
}
if (S.inTomb) {
// (free return glyph REMOVED, Galen Aug 20: it drew a second portal 1.1u
// in front of vf-tomb's wraith-gated exit and always fired first — the
// earned exit (+300, clear-all-three) was unreachable and the fight
// skippable. vf-tomb's portal is now the ONE way out; it reseals the lid.)
if (pz < 88 && pz > 20) S.inTomb = false
}
}
} catch (e) {}
hook · ix-tween
reality tween — 2.4s metamorphosis; shadows/AO decoupled from the double-eval
// ix-tween — THE REALITY TWEEN (Galen: "transition entire scenes using shader
// graphics... one room leads to the state of the other"). A teleport must not CUT.
// This node AUTO-DETECTS position discontinuities (a jump farther than any legal
// step) and publishes the OLD camera pose + a decaying tween scalar to its owned
// lane u176-182. The s3 primary ray then dissolves per-pixel from the old reality
// into the new (noise-threshold selection — ONE march per pixel, no double cost),
// with a reality-knit glow at the frontier. Because detection is automatic, EVERY
// portal in the world (den mouth, dimension traps, jump pad, respawn) gets the
// tween for free — no portal code changes.
// Lane: u176 morph01 · u173-175 frozen jump offset (old scene -> here). MORPH v2:
// the shader blends w3_map(p+offset) into w3_map(p) — true scene metamorphosis
// (Galen: forms meld into their nearest like thing), not a pixel dissolve.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
const yaw = V.yaw || 0, pitch = V.pitch || 0
const cp = Math.cos(pitch)
const fx = Math.sin(yaw) * cp, fy = Math.sin(pitch), fz = Math.cos(yaw) * cp
// WORKING STATE lives WORKER-GLOBAL, never in worldData — so it can NEVER persist
// into a saved world and fire a phantom morph on restart (a page reload resets the
// worker; a fresh load re-baselines at spawn → no jump → no morph). This is the
// hard guarantee that restart-to-pure holds.
if (!globalThis.__VF_TWEEN) globalThis.__VF_TWEEN = { t: 0, lx: px, ly: py, lz: pz, ltx: px + fx, lty: py + fy, ltz: pz + fz, tpSeen: (wd.__tpMorph || 0) }
const W = globalThis.__VF_TWEEN
const dtc = Math.min(dt || 0.016, 1 / 30)
const jump = Math.hypot(px - W.lx, py - W.ly, pz - W.lz)
// legal movement tops out well under 1u/tick; 3.5u in one tick = a teleport
const tpFlagged = (wd.__tpMorph || 0) !== W.tpSeen;
W.tpSeen = (wd.__tpMorph || 0);
if (jump > 3.5 || tpFlagged) {
W.t = 1
// freeze the jump offset: old-scene space brought to where you now stand
W.dx = W.lx - px; W.dy = W.ly - py; W.dz = W.lz - pz
// ROOM GRAPH labels: the morph knows WHAT it crosses. wd.__room still holds
// the pre-jump room (vf-room-graph runs after us); the graph node names the
// arrival by filling the null.
wd.__tweenFrom = wd.__room || null
wd.__tweenTo = null
}
if (W.t > 0) W.t = Math.max(0, W.t - dtc / 2.4) // ~2.4s metamorphosis (Galen: slower, so the melding reads as the beautiful transition it is; shadows/AO no longer pay the double-eval, so longer ≠ laggier)
// remember this tick's pose for next tick's comparison
W.lx = px; W.ly = py; W.lz = pz
W.ltx = px + fx; W.lty = py + fy; W.ltz = pz + fz
// publish — eased so the dissolve lingers at the start and snaps home
const e = W.t * W.t * (3 - 2 * W.t)
u[176] = e
// offset published to u173-175 (MOVED off u177-179: Opus's sarcophagus lid u177
// + tomb unlight u178-182 live there — my morph was clobbering the portal-in).
if (e > 0) { u[173] = W.dx || 0; u[174] = W.dy || 0; u[175] = W.dz || 0 }
}
} catch (e) {}
hook · vf-room-graph
by claude-code
// vf-room-graph — THE ROOM GRAPH seed (Galen's next core push: "the system has no
// way to understand room in connection"). The engine's first first-class PLACES:
// worldData.__rooms = { id: { name, node, bounds{x,y,z,hx,hy,hz},
// connections: [{to, via: door|portal|stair|carve|warp, at{x,y,z}, gated?}] } }
// Pure data (worldData-legal), transcribed from vf-frame's walkable() truth — the
// graph that was always there in disguise. Legacy-neutral: nothing consumes it yet
// except wd.__room (live whereIs) + the tween's from/to labels.
// Consumers (lane split on the commons): VISIT (opus-a) · stitching (Fable·E) ·
// tween labels + whereIs (this node).
try {
const wd = sim.worldData, V = wd.__vf
if (V) {
const REV = 'rg1'
if (!wd.__rooms || wd.__rooms.__rev !== REV) {
wd.__rooms = {
__rev: REV,
nave: { name: 'THE NAVE', node: 'vf-frame', bounds: { x: 0, y: 2.2, z: 0, hx: 4, hy: 2.2, hz: 9 },
connections: [
{ to: 'warren', via: 'door', at: { x: 0, y: 1.5, z: -9.4 } }, // the lancet arch
{ to: 'backroom', via: 'carve', at: { x: 0, y: 1.5, z: 8.8 }, gated: '__VF_SECRET0' },
{ to: 'side-chamber', via: 'door', at: { x: 4.5, y: 1.2, z: 0 } },
{ to: 'crypt', via: 'carve', at: { x: -4, y: 1.4, z: -5 }, gated: '__VF_CRYPT0' }] },
backroom: { name: 'THE BACKROOM', node: 'vf-backroom', bounds: { x: 0, y: 1.6, z: 13.25, hx: 4, hy: 1.6, hz: 4.25 },
connections: [
{ to: 'nave', via: 'carve', at: { x: 0, y: 1.5, z: 9 }, gated: '__VF_SECRET0' },
{ to: 'void-chamber', via: 'portal', at: { x: 3, y: 0.3, z: 16 } }] },
warren: { name: 'THE WARREN · ROOM A', node: 'vf-frame', bounds: { x: 0, y: 1.8, z: -16.7, hx: 4, hy: 1.8, hz: 7.3 },
connections: [
{ to: 'nave', via: 'door', at: { x: 0, y: 1.5, z: -9.4 } },
{ to: 'warren-b', via: 'warp', at: { x: 0, y: 1.5, z: -12.5 } }, // the column commit
{ to: 'corridor', via: 'door', at: { x: 0, y: 1.5, z: -24 }, gated: 'doorOpen' }] },
'warren-b': { name: 'THE WARREN · ROOM B (grand vault)', node: 'vf-frame', bounds: { x: 0, y: 1.8, z: -21.2, hx: 4, hy: 1.8, hz: 8.8 },
connections: [{ to: 'warren', via: 'warp', at: { x: 0, y: 1.5, z: -12.5 } }] },
corridor: { name: 'THE APPROACH CORRIDOR', node: 'vf-frame', bounds: { x: 0, y: 1.6, z: -39, hx: 2.6, hy: 1.6, hz: 15 },
connections: [
{ to: 'warren', via: 'door', at: { x: 0, y: 1.5, z: -24 }, gated: 'doorOpen' },
{ to: 'avenue', via: 'door', at: { x: 0, y: 1.5, z: -54 } }] },
avenue: { name: 'THE RISEN NAVE', node: 'vf-frame', bounds: { x: 0, y: 3, z: -74.5, hx: 5.75, hy: 3, hz: 20.5 },
connections: [
{ to: 'corridor', via: 'door', at: { x: 0, y: 1.5, z: -54 } },
{ to: 'arena', via: 'door', at: { x: 4.012, y: 1.5, z: -95 } }, // the gable tunnel
{ to: 'den', via: 'portal', at: { x: 0, y: 1.5, z: -75 } }] },
arena: { name: 'THE OCTAGON ARENA', node: 'vf-arena-dragon', bounds: { x: 4.012, y: 2.5, z: -106, hx: 9, hy: 2.5, hz: 9 },
connections: [{ to: 'avenue', via: 'door', at: { x: 4.012, y: 1.5, z: -95 } }] },
'side-chamber': { name: 'THE SIDE CHAMBER (time cells)', node: 'vf-timecells', bounds: { x: 8, y: 1.6, z: 0, hx: 3, hy: 1.6, hz: 3.5 },
connections: [
{ to: 'nave', via: 'door', at: { x: 4.5, y: 1.2, z: 0 } },
{ to: 'hall', via: 'door', at: { x: 10.7, y: 1.2, z: 0 } }] },
hall: { name: 'THE ESCAPE HALL', node: 'vf-timecells', bounds: { x: 14.25, y: 1.2, z: 0, hx: 3.75, hy: 1.2, hz: 1 },
connections: [
{ to: 'side-chamber', via: 'door', at: { x: 10.7, y: 1.2, z: 0 } },
{ to: 'woven', via: 'door', at: { x: 18, y: 1.2, z: 0 } }] },
woven: { name: 'THE WOVEN ROOM', node: 'vf-lawform', bounds: { x: 22.25, y: 1.5, z: 0, hx: 4.25, hy: 1.5, hz: 3 },
connections: [{ to: 'hall', via: 'door', at: { x: 18, y: 1.2, z: 0 } }] },
'void-chamber': { name: 'THE VOID CHAMBER', node: 'vf-voidroom', bounds: { x: 0, y: 1.6, z: 40, hx: 4, hy: 1.6, hz: 4 },
connections: [{ to: 'backroom', via: 'portal', at: { x: 0, y: 1.5, z: 40 } }] },
'lurker-dim': { name: 'THE LURKER DIMENSION', node: 'vf-veil-lurker', bounds: { x: 0, y: 1.8, z: 62, hx: 5, hy: 1.8, hz: 5.5 },
connections: [{ to: 'den', via: 'portal', at: { x: 0, y: 1.5, z: 62 } }] },
den: { name: 'THE DEN INTERIOR', node: 'vf-veil-lurker', bounds: { x: 0, y: 2, z: 82, hx: 5.6, hy: 2, hz: 5.15 },
connections: [
{ to: 'avenue', via: 'portal', at: { x: 0, y: 1.5, z: 77.2 } },
{ to: 'lurker-dim', via: 'portal', at: { x: 3.6, y: 0.5, z: 85 }, gated: 'orbShot' },
{ to: 'lurker-dim', via: 'stair', at: { x: 1.3, y: 4.5, z: 82.6 } }] },
crypt: { name: 'THE CRYPT', node: 'vf-crypt-room', bounds: { x: -7.3, y: 1.6, z: -5.0, hx: 3.3, hy: 1.6, hz: 2.4 },
connections: [{ to: 'nave', via: 'carve', at: { x: -4, y: 1.4, z: -5 }, gated: '__VF_CRYPT0' }] }
}
}
// whereIs — live point→room lookup (bounds containment; rooms are disjoint)
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
let now = null
const R = wd.__rooms
for (const id in R) {
if (id === '__rev') continue
const b = R[id].bounds
if (Math.abs(px - b.x) <= b.hx && Math.abs(pz - b.z) <= b.hz &&
Math.abs(py - b.y) <= b.hy + 1.2) {
// warren A vs B share space — the warp state decides
if (id === 'warren' && (V.warp || 0) < 0) continue
if (id === 'warren-b' && (V.warp || 0) >= 0) continue
now = id; break
}
}
wd.__room = now
globalThis.__ROOM_NOW = now
// tween handshake: ix-tween sets __tweenTo = null on a jump; we name the arrival
if (wd.__tweenTo === null && now) wd.__tweenTo = now
}
} catch (e) {}
hook · vf-relics
by claude-code
// vf-relics — THE THREE RELICS (Galen): a shard from the DRAGON (its crystal), the
// LURKER, and the TOMB (underfloor). Shown as three shiny gems on the HUD right edge
// above ammo (u190 = collected bitmask). Collect all three → they COMBINE (u191) and
// the walled-off gable at the risen nave's far end becomes something new.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
if (!V.__relics) V.__relics = { got: [false, false, false], drop: [null, null, null], combined: 0 }
const R = V.__relics
const px = V.px || 0, pz = V.pz || 0, t = V.t || 0
// 0 — DRAGON: its crystal (already a pickup you grab)
R.got[0] = !!V.hasCrystal
// UNIFY (Galen: "the 3 crystals as agreed"): the den orb-solve crystal IS the
// LURKER relic (the den is the lurker's domain) and the sarcophagus crystal IS
// the TOMB relic — so the crystals you actually collect feed the gate, either via
// these puzzles OR via the death-shards below.
if (V.__denCrystal) R.got[1] = true
if (V.__cryptCrystal) R.got[2] = true
// 1 — LURKER: on its death a shard drops at the kill spot
const L = V.__lurker
if (L && L.dead && !R.got[1] && !R.drop[1]) R.drop[1] = { x: L.x || -6.5, y: 0.55, z: L.z || -72 }
// 2 — TOMB (underfloor): clearing the wraiths raises a shard in the tomb
const T = V.__tomb
if (T && T.cleared && !R.got[2] && !R.drop[2]) R.drop[2] = { x: 0, y: 0.55, z: 98.5 }
// render the dropped shards + collect on walk-over
for (let i = 1; i < 3; i++) {
const D = R.drop[i]
if (D && !R.got[i] && Array.isArray(V.pop)) {
V.pop.push(D.x, D.y + 0.18 * Math.sin(t * 3 + i), D.z, 12, 0.95, 0, 0, 0)
for (let s = 0; s < 3; s++) { const a = t * 2 + s * 2.094; V.pop.push(D.x + Math.cos(a) * 0.42, D.y + 0.35, D.z + Math.sin(a) * 0.42, 5, 0.5, 0, 0, 0) }
if (Math.hypot(px - D.x, pz - D.z) < 1.1) {
R.got[i] = true
wd.__play_sound = [{ frequency: 900, duration: 0.12, volume: 0.2, type: 'sine' }, { frequency: 1500, duration: 0.16, volume: 0.13, type: 'triangle' }]
}
}
}
let mask = 0; for (let i = 0; i < 3; i++) if (R.got[i]) mask += (1 << i)
u[190] = mask
const all = R.got[0] && R.got[1] && R.got[2]
if (all && !R.combined) {
R.combined = 1
wd.__play_sound = [{ frequency: 220, duration: 0.6, volume: 0.24, type: 'sine' }, { frequency: 440, duration: 0.5, volume: 0.16, type: 'triangle' }, { frequency: 880, duration: 0.7, volume: 0.12, type: 'sine' }, { frequency: 1760, duration: 0.5, volume: 0.08, type: 'triangle' }]
}
// COMBINED — radiant GATEWAY particles at the opened gable (no s3 edit needed)
if (all && Array.isArray(V.pop)) {
for (let k = 0; k < 10; k++) { const a = (t * 1.2 + k * 0.628)
V.pop.push(Math.cos(a) * 2.4, 0.6 + (Math.sin(a) * 0.5 + 0.5) * 4.4, -95.7, 7, 0.7, 0, 0, 0)
}
for (let k = 0; k < 4; k++) V.pop.push(Math.sin(t + k) * 1.5, 1.0 + k * 0.9, -95.7, 12, 0.6, 0, 0, 0)
}
u[191] = all ? 1 : 0
globalThis.__VF_RELICS_ALL = all ? 1 : 0 // JS collision (vf-frame city walkable) reads this
}
} catch (e) {}
hook · vf-city-crossing
ACT II city crossing — ember ball + hud + boom; Sep 2 unstick: open mouth, held rides hand, ball-search watchdog, damped leash
// vf-city-crossing — ACT II · THE CITY CROSSING. NOCTURNE DISTRICT's mechanics ported
// into the dome ("the city as a pinball table", constants ÷4: their 4 game units = 1
// world unit). THE EMBER BALL is weapon slot 3 (granted + auto-selected at the gate):
// HOLD fire = draw the ball to your hand · it grabs when it arrives · FLICK the look
// and RELEASE = a real throw. Red interceptors run the centre street for the GATE:
// dull-red drifters weave · crimson dodgers juke your throws · blazing hunters burn
// for the line. A fast ball (>15 u/s) smashes them; a slow held ball in traffic gets
// RAMMED out of your hand. Three ships through the gate = the night resets you to the
// mouth (the reality tween carries it). Ten kills clears a wave — a heart comes back.
// Reach the far plaza = THE CITY IS CROSSED (full hearts, V.cityCrossed latches).
// Owns u83-90 (ball) · u91-99 (ship pos) · u133-139 (boom + hud) · u141-149 (pose).
// Working state on globalThis.__VF_NCX ONLY (never worldData — restart stays pure).
// Geometry mirrors vf-frame's city walkable (dome r20 @ (0,-118), towers on the 7u
// grid, footprint half 1.8, gate mouth z≈-99.5) — keep in sync if the city changes.
// UNSTUCK LAWS (Sep 2): the gate mouth is OPEN for the ball (street gutter |x|<=1.7
// is legal — it follows you out; the mouth PILLARS still bounce wide throws); a HELD
// ball rides the hand with no wall clamps; and a BALL SEARCH watchdog re-kindles a
// wedged ember (no progress toward its target for 1.5s while wanted >5.5u away —
// a progress test, so tower-pocket oscillation can't fool it) — flash + chime.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u) && u.length >= 150) {
const dtc = Math.min(dt || 0.016, 0.05)
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
const inCity = (globalThis.__VF_RELICS_ALL || 0) > 0.5 && pz < -85 && (V.warp || 0) >= 0 && ((u[43] || 0) < 1.5) // CATHEDRAL reality only — the city never activates in the dragon wing (u43==2), so it can't overwrite the arena
let G = globalThis.__VF_NCX
if (!inCity) {
// freeze the night, don't wipe it — retreat through the gate isn't a reset
if (G && G.on) V.hasBall = 0
u[83] = 0; u[92] = 0; u[95] = 0; u[98] = 0; u[136] = 0; u[138] = 0; u[139] = 0
} else {
if (!G || !G.on) {
G = globalThis.__VF_NCX = {
on: 1, t: 0, intro: 0,
bx: 0, by: 1.5, bz: -102, vx: 0, vy: 0, vz: 0,
held: 0, free: 0, flash: 0, ramCd: 0, red: 0, danger: 0,
hvx: 0, hvy: 0, hvz: 0, phx: null, phy: 0, phz: 0, pvx: 0, pvy: 0, pvz: 0,
ships: [null, null, null], spawnT: 2.0,
kills: 0, through: 0, wave: 1, crossed: 0,
boom: null, sonarT: 0, mWas: 0,
minD: null, prgT: 0
}
}
G.t += dtc
const SND = []
V.hasBall = 1
if (!G.intro) { G.intro = 1; if (V.weapon === 1) V.weapon = 3 } // hand them the ball once
// ── the hand point: 2.8u along the look ray ────────────────────────────
const yaw = V.yaw || 0, pitch = V.pitch || 0, cp = Math.cos(pitch)
const fx = Math.sin(yaw) * cp, fy = Math.sin(pitch), fz = Math.cos(yaw) * cp
const hx = px + fx * 2.8, hy = Math.max(0.5, Math.min(5.5, py + fy * 2.8)), hz = pz + fz * 2.8
const inp = wd.input || {}, ptr = inp.pointer || {}
const holdBall = (V.weapon === 3) && !!ptr.pressed
G.ramCd = Math.max(0, G.ramCd - dtc)
G.flash *= Math.exp(-4 * dtc)
G.red = (G.red || 0) * Math.exp(-2.5 * dtc)
G.pvx = G.vx; G.pvy = G.vy; G.pvz = G.vz
// ── BALL: draw-to-hand / grab / throw (Nocturne ÷4, FPS hand = look ray) ─
if (holdBall) {
if (G.phx == null) { G.phx = hx; G.phy = hy; G.phz = hz }
const cvx = (hx - G.phx) / Math.max(dtc, 1e-4), cvy = (hy - G.phy) / Math.max(dtc, 1e-4), cvz = (hz - G.phz) / Math.max(dtc, 1e-4)
G.hvx = G.hvx * 0.6 + cvx * 0.4; G.hvy = G.hvy * 0.6 + cvy * 0.4; G.hvz = G.hvz * 0.6 + cvz * 0.4
G.phx = hx; G.phy = hy; G.phz = hz
const dx = hx - G.bx, dy = hy - G.by, dz = hz - G.bz
const dd = Math.hypot(dx, dy, dz) || 1
if (!G.mWas) SND.push({ frequency: 420, duration: 0.06, volume: 0.22, type: 'sine' })
if (G.ramCd <= 0 && (G.held || dd < 1.6)) {
if (!G.held) SND.push({ frequency: 560, duration: 0.05, volume: 0.16, type: 'sine' })
G.held = 1; G.free = 0
const k = Math.min(1, dtc * 16)
G.bx += dx * k; G.by += dy * k; G.bz += dz * k
const cl = (v) => Math.max(-95, Math.min(95, v))
G.vx = cl(G.hvx); G.vy = cl(G.hvy); G.vz = cl(G.hvz)
} else {
const pull = 375 * Math.min(1, dd / 27.5)
G.vx += (dx / dd) * pull * dtc; G.vy += (dy / dd) * pull * dtc; G.vz += (dz / dd) * pull * dtc
const dr = 1 - 1.7 * dtc
G.vx *= dr; G.vy *= dr; G.vz *= dr
}
G.flash = Math.max(G.flash, 0.2)
} else {
if (G.mWas) { // release = the throw
SND.push({ frequency: 300, duration: 0.08, volume: 0.22, type: 'triangle' })
if (G.held && Math.hypot(G.vx, G.vy, G.vz) > 22.5) G.free = 1
G.held = 0; G.phx = null; G.hvx = 0; G.hvy = 0; G.hvz = 0
}
if (G.free) {
const dr = 1 - 0.22 * dtc
G.vx *= dr; G.vy *= dr; G.vz *= dr
if (Math.hypot(G.vx, G.vy, G.vz) < 17.5) G.free = 0
}
if (!G.free) { // leash — the ember drifts home, 4.5u ahead of you
const hn = Math.max(cp, 0.3)
const lx = px + (fx / hn) * 4.5, lz = pz + (fz / hn) * 4.5
G.vx += (lx - G.bx) * 0.9 * dtc + Math.sin(G.t * 0.9) * 1.5 * dtc
G.vz += (lz - G.bz) * 0.9 * dtc
G.vy += (1.6 - G.by) * 2.2 * dtc
const ld = 1 - 0.8 * dtc // damp the spring or the ember orbits forever
G.vx *= ld; G.vy *= ld; G.vz *= ld
const sp = Math.hypot(G.vx, G.vy, G.vz)
if (sp > 9) { const k = 9 / sp; G.vx *= k; G.vy *= k; G.vz *= k }
}
}
G.mWas = holdBall ? 1 : 0
// vertical: held rides the hand; free falls and stays lively (auto-kick)
if (G.free) {
G.vy -= 9 * dtc
if (G.by < 0.75 && Math.abs(G.vy) < 2.5) G.vy = 7
}
{ const sp = Math.hypot(G.vx, G.vy, G.vz); if (sp > 95) { const k = 95 / sp; G.vx *= k; G.vy *= k; G.vz *= k } }
// integrate + collide — a HELD ball rides the hand (no wall clamps); loose
// balls: table = dome shell + tower kickers, street = walled gutter, and the
// gate mouth is OPEN over the street gap (|x|<=1.7) so the ember follows you
if (!G.held) {
G.bx += G.vx * dtc; G.by += G.vy * dtc; G.bz += G.vz * dtc
const rest = G.free ? 0.75 : 1
if (G.by < 0.65) { G.by = 0.65; G.vy = Math.abs(G.vy) * 0.9; G.flash = Math.max(G.flash, 0.7) }
if (G.by > 5.8) { G.by = 5.8; G.vy = Math.min(G.vy, 0) }
if (G.bz <= -99.6) {
// ON THE TABLE — dome shell + tower super-bounce
const cx = G.bx, cz = G.bz + 118, rr = Math.hypot(cx, cz)
if (rr > 19.3) { const nx = cx / rr, nz = cz / rr; G.bx = nx * 19.3; G.bz = nz * 19.3 - 118; const vn = G.vx * nx + G.vz * nz; if (vn > 0) { G.vx -= (1 + rest) * vn * nx; G.vz -= (1 + rest) * vn * nz } }
if (rr < 19.5) { // towers exist only inside the dome
const md = (a) => ((a % 1) + 1) % 1
const gx = (md(G.bx / 7) - 0.5) * 7, gz = (md(G.bz / 7) - 0.5) * 7
const qx = Math.max(-1.8, Math.min(1.8, gx)), qz = Math.max(-1.8, Math.min(1.8, gz))
const ddx = gx - qx, ddz = gz - qz, dd = Math.hypot(ddx, ddz)
const RB = 0.7
if (dd < RB && (Math.abs(gx) < 1.8 + RB && Math.abs(gz) < 1.8 + RB)) {
const nx = dd > 1e-5 ? ddx / dd : 1, nz = dd > 1e-5 ? ddz / dd : 0
G.bx += nx * (RB - dd); G.bz += nz * (RB - dd)
const vn = G.vx * nx + G.vz * nz
if (vn < 0) {
const kr = G.free ? 1.75 : 1.9 // the pinball kicker — super-elastic
G.vx -= kr * vn * nx; G.vz -= kr * vn * nz
if (-vn > 10) { G.flash = 1; SND.push({ frequency: 150, duration: 0.09, volume: 0.2, type: 'triangle' }) }
}
}
}
} else if (Math.abs(G.bx) > 1.7) {
// THE MOUTH PILLARS — only the street gap is open; wide exits bounce back
G.bz = -99.6; G.vz = -Math.abs(G.vz) * rest
} else {
// THE STREET — walled gutter between the gate and the cathedral steps
if (G.bx > 1.7) { G.bx = 1.7; G.vx = -Math.abs(G.vx) * rest }
if (G.bx < -1.7) { G.bx = -1.7; G.vx = Math.abs(G.vx) * rest }
if (G.bz > -86) { G.bz = -86; G.vz = -Math.abs(G.vz) * rest }
}
{ const dvx = G.vx - G.pvx, dvy = G.vy - G.pvy, dvz = G.vz - G.pvz
if (dvx * dvx + dvy * dvy + dvz * dvz > 56) G.flash = 1 }
// ── BALL SEARCH — an ember that stops CLOSING on where it's wanted for
// 1.5s (wedged behind a tower, pinned in a pocket — oscillation can't
// fool a progress test) re-kindles to your hand: flash + chime
if (holdBall || !G.free) {
const hn2 = Math.max(cp, 0.3)
const tgx = holdBall ? hx : px + (fx / hn2) * 4.5, tgz = holdBall ? hz : pz + (fz / hn2) * 4.5
const d = Math.hypot(tgx - G.bx, tgz - G.bz)
if (G.minD == null || d < G.minD - 0.25) { G.minD = d; G.prgT = 0 }
else { G.prgT = (G.prgT || 0) + dtc }
if (G.prgT > 1.5 && d > 5.5) {
G.bx = hx; G.by = Math.max(0.75, Math.min(5.5, hy)); G.bz = hz
G.vx = 0; G.vy = 0; G.vz = 0; G.free = 0
G.minD = null; G.prgT = 0
G.flash = 1
SND.push({ frequency: 740, duration: 0.09, volume: 0.18, type: 'sine' }, { frequency: 988, duration: 0.12, volume: 0.14, type: 'sine' })
}
} else { G.minD = null; G.prgT = 0 }
} else { G.minD = null; G.prgT = 0 }
// ── INTERCEPTORS: spawn far, run the centre street for the gate ─────────
const GOAL = -100.5
const bsp = Math.hypot(G.vx, G.vy, G.vz)
const heat = Math.min(1, (G.kills + G.through * 2) / 24)
G.spawnT -= dtc
G.danger = 0
let nearCls = 0
for (let i = 0; i < 3; i++) {
let E = G.ships[i]
if (!E) {
if (false) { // INTERCEPTORS RETIRED (Aug 30) — the citys flyers are THE BURNING FIVE (vf-city-crucifix); ball + hud + boom stay
const roll = Math.random()
const cls = roll < 0.24 + heat * 0.3 ? 2 : (roll < 0.6 ? 1 : 0)
G.ships[i] = { x: (Math.random() - 0.5) * 2.4, z: -133.5 - Math.random() * 2, a: 1.6 + Math.random() * 2.2, sp: (cls === 2 ? 4.75 : 3.25) + Math.random() * 2.5, ph: Math.random() * 6.28, cls, lvx: 0, flare: 0, dcd: 0, yaw: Math.PI, bank: 0 }
G.spawnT = Math.max(1.6, 2.5 + Math.random() * 2.5 - heat * 1.2)
if (cls === 2) SND.push({ frequency: 68, duration: 0.35, volume: 0.11, type: 'sawtooth' })
}
continue
}
E.dcd = Math.max(0, E.dcd - dtc)
E.flare *= Math.exp(-3 * dtc)
let sp = E.sp
if (E.cls === 2) { // the hunter burns for the line
const near = Math.max(0, 1 - (GOAL - E.z) / 30)
sp *= 1 + near * 1.3
E.flare = Math.max(E.flare, near * 0.8)
}
E.z += sp * dtc
const wx2 = Math.sin(G.t * 1.3 + E.ph) * (E.cls === 0 ? 1.75 : 1.0)
if (E.cls >= 1 && E.dcd <= 0) { // dodgers + hunters juke a thrown ball
const bd = Math.hypot(G.bx - E.x, G.bz - E.z)
const closing = ((E.x - G.bx) * G.vx + (E.z - G.bz) * G.vz) / Math.max(bd, 0.25)
if (bd < 8.5 && closing > 17.5) {
E.lvx = (G.bx > E.x ? -1 : 1) * (E.cls === 2 ? 13.75 : 18.75)
E.dcd = 1.1; E.flare = 1
SND.push({ frequency: 480, duration: 0.07, volume: 0.12, type: 'triangle' }, { frequency: 700, duration: 0.09, volume: 0.1, type: 'triangle' })
}
}
E.lvx *= Math.exp(-2.2 * dtc)
E.x = Math.max(-1.55, Math.min(1.55, E.x + (wx2 + E.lvx) * dtc))
const steer = wx2 + E.lvx
E.yaw = Math.PI + Math.max(-0.6, Math.min(0.6, -steer / 13.75))
E.bank = Math.max(-0.8, Math.min(0.8, steer / 11.25))
const hd = Math.hypot(G.bx - E.x, G.bz - E.z)
if (hd < 2.0 && Math.abs(G.by - E.a) < 2.0 && G.held && bsp <= 15) {
// RAMMED — a slow held ball in traffic is knocked out of your hand
G.held = 0; G.free = 1; G.ramCd = 0.8
const tpx = px - G.bx, tpz = pz - G.bz, tpd = Math.hypot(tpx, tpz) || 1
G.vx = (G.bx - E.x) * 4.5 + E.lvx * 2 + (tpx / tpd) * 25
G.vz = (tpz / tpd) * 25; G.vy = 6
G.flash = 1; G.red = Math.max(G.red, 0.6)
SND.push({ frequency: 180, duration: 0.25, volume: 0.4, type: 'sawtooth' }, { frequency: 120, duration: 0.35, volume: 0.3, type: 'square' })
G.ships[i] = null
continue
}
if (hd < 1.8 && Math.abs(G.by - E.a) < 2.0 && bsp > 15) {
// SMASHED
G.kills++; G.flash = 1
G.boom = { x: E.x, a: E.a, z: E.z, t: 0.001, cls: E.cls }
V.score = (V.score || 0) + 150
if (Array.isArray(V.deaths)) V.deaths.push({ x: E.x, y: E.a, z: E.z })
SND.push({ frequency: 660, duration: 0.22, volume: 0.4, type: 'square' })
if (E.cls === 2) SND.push({ frequency: 880, duration: 0.3, volume: 0.22, type: 'square' }, { frequency: 1320, duration: 0.4, volume: 0.15, type: 'sine' })
G.ships[i] = null
if (G.kills >= G.wave * 10) { // wave cleared — a heart comes back
G.wave++; G.through = Math.max(0, G.through - 1)
SND.push({ frequency: 523, duration: 0.14, volume: 0.2, type: 'triangle' }, { frequency: 784, duration: 0.16, volume: 0.18, type: 'triangle' }, { frequency: 1046, duration: 0.3, volume: 0.14, type: 'sine' })
}
continue
}
if (E.z > GOAL) {
// SLIPPED THROUGH THE GATE — a heart is gone
G.through++; G.red = 1
SND.push({ frequency: 90, duration: 0.4, volume: 0.5, type: 'sawtooth' })
G.ships[i] = null
if (G.through >= 3) {
// THE NIGHT RESETS — back to the gate mouth (the reality tween fires on the jump)
SND.push({ frequency: 220, duration: 0.3, volume: 0.3, type: 'sawtooth' }, { frequency: 110, duration: 0.4, volume: 0.3, type: 'sawtooth' }, { frequency: 55, duration: 0.7, volume: 0.3, type: 'sine' })
G.through = 0; G.kills = 0; G.wave = 1
G.ships = [null, null, null]; G.spawnT = 3
G.bx = 0; G.by = 1.5; G.bz = -102; G.vx = 0; G.vy = 0; G.vz = 0; G.held = 0; G.free = 0
V.px = 0; V.pz = -96.5
}
continue
}
const dgr = Math.max(0, 1 - (GOAL - E.z) / 17.5)
if (dgr > G.danger) { G.danger = dgr; nearCls = E.cls }
}
// proximity sonar — quickens as a ship closes on the gate
if (G.danger > 0.12) {
G.sonarT -= dtc
if (G.sonarT <= 0) {
G.sonarT = 0.95 - G.danger * 0.75
SND.push({ frequency: nearCls === 2 ? 1170 : (nearCls === 1 ? 990 : 880), duration: 0.05, volume: 0.045 + G.danger * 0.07, type: 'square' })
}
} else { G.sonarT = 0 }
// ── THE CROSSING — reach the far plaza and the night is yours ──────────
if (!G.crossed && pz < -132) {
G.crossed = 1; G.wave++; G.through = 0; V.cityCrossed = 1
SND.push({ frequency: 392, duration: 0.16, volume: 0.22, type: 'triangle' }, { frequency: 523, duration: 0.16, volume: 0.2, type: 'triangle' }, { frequency: 659, duration: 0.18, volume: 0.18, type: 'triangle' }, { frequency: 1046, duration: 0.5, volume: 0.16, type: 'sine' })
}
if (G.crossed && pz > -112) G.crossed = 0
// boom shell clock
if (G.boom) { G.boom.t += dtc / 0.7; if (G.boom.t >= 1) G.boom = null }
// ── publish ─────────────────────────────────────────────────────────────
u[83] = 1
u[84] = G.bx; u[85] = G.by; u[86] = G.bz
u[87] = G.flash
u[88] = G.held ? 1 : (G.free ? 2 : 0)
u[89] = G.red
u[90] = G.danger
for (let i = 0; i < 3; i++) {
const E = G.ships[i]
u[91 + i * 3] = E ? E.x : 0
u[92 + i * 3] = E ? E.a : 0
u[93 + i * 3] = E ? E.z : 0
u[141 + i * 3] = E ? E.yaw : 0
u[142 + i * 3] = E ? E.bank : 0
u[143 + i * 3] = E ? (E.cls + Math.min(0.95, E.flare)) : 0
}
u[133] = G.boom ? G.boom.x : 0
u[134] = G.boom ? G.boom.a : 0
u[135] = G.boom ? G.boom.z : 0
u[136] = G.boom ? Math.min(0.999, G.boom.t) : 0
u[137] = G.boom ? G.boom.cls : 0
u[138] = Math.max(0, 3 - G.through)
u[139] = G.wave * 100 + Math.max(0, G.kills - (G.wave - 1) * 10)
if (SND.length) wd.__play_sound = SND
}
}
} catch (e) {}
hook · vf-tomb
TOMB dimension — wraiths flee unlight; exit lands at live sarcophagus (Aug 20)
// vf-tomb — THE TOMB DIMENSION: the pocket you fall into through the sarcophagus.
// TWISTS on the game's own mechanics (Galen):
// · INVERTED LANTERN — here reality is the default; your thrown sphere UNMAKES
// it instead (u178 = unlight sphere) — the floor you stand on vanishes in the
// light, so you must AVOID your own crystal.
// · RISEN WATCHERS — three tomb-wraiths orbit; they're only mortal while OUTSIDE
// the unlight (reality) — the reverse of the lurker, who was only mortal inside.
// · THE EXIT is the same return-glyph grammar, but it only appears once all three
// wraiths are down. Coordinates live here only.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (V && Array.isArray(u)) {
const inTomb = (V.pz || 0) > 91 && (V.pz || 0) < 103 && Math.abs(V.px || 0) < 6
if (!V.__tomb) V.__tomb = { w: null, cleared: 0, t0: null }
const T = V.__tomb
if (!inTomb) { u[178] = 0; if (T.w) T.w = null; return }
const C = { x: 0, z: 97 }
const px = V.px || 0, pz = V.pz || 0, t = V.t || 0
const sdt = Math.min(dt || 0.016, 1 / 30)
const L = V.lantern
// INVERTED LANTERN — publish the UNLIGHT sphere (the ix tomb geometry dissolves
// its floor inside it; here light is danger, not safety).
if (L && L.deployed) { u[178] = 1; u[179] = L.x; u[180] = L.y; u[181] = L.z; u[182] = L.rad || 0 }
else { u[178] = 0 }
// fall into the unlight = you drop out of the world (reset to the entry ledge)
if (L && L.deployed && Math.hypot(px - L.x, pz - L.z) < (L.rad || 0) * 0.7 && (V.py || 0) < 1.4) {
V.px = 0; V.pz = 93.5; V.py = 1.7; V.hp = Math.max(0.05, (V.hp != null ? V.hp : 1) - 0.15)
if (wd.__play_sound == null) wd.__play_sound = [{ frequency: 40, duration: 0.6, volume: 0.3, type: 'sawtooth' }]
}
// RISEN WATCHERS — three wraiths; mortal ONLY when OUTSIDE the unlight (reality)
if (!T.w) { T.grace = 3.0; T.w = [ // SPAWN GRACE — the drop is safe
{ x: C.x - 2.4, z: C.z + 1.6, hp: 1, ph: 0, dead: 0 }, // seeded on the FAR side of the
{ x: C.x + 2.4, z: C.z + 1.6, hp: 1, ph: 2.1, dead: 0 }, // pocket, away from the entry ledge
{ x: C.x, z: C.z + 2.6, hp: 1, ph: 4.2, dead: 0 }] }
T.grace = Math.max(0, (T.grace == null ? 3.0 : T.grace) - sdt)
let alive = 0
for (const w of T.w) {
if (w.dead) continue
alive++
w.ph += sdt * 1.3
// slow orbit + drift toward player
const ang = w.ph
const ox = C.x + Math.cos(ang) * 2.6, oz = C.z + Math.sin(ang) * 2.6
const drift = T.grace > 0 ? 0 : 0.006 // no hunting during grace
w.x += (ox - w.x) * 0.04 + (px - w.x) * drift
w.z += (oz - w.z) * 0.04 + (pz - w.z) * drift
if (T.grace > 0) { // and they cannot close on the ledge
const ddp = Math.hypot(px - w.x, pz - w.z) || 1
if (ddp < 2.2) { w.x += ((w.x - px) / ddp) * (2.2 - ddp); w.z += ((w.z - pz) / ddp) * (2.2 - ddp) }
}
// REALITY-BEINGS REFUSE THE UNLIGHT (Galen, Aug 20: a crystal thrown into
// the pocket covered the whole orbit — permanently lit wraiths read as
// INVINCIBLE and the gun read as broken). The unlight unmakes reality, so
// its creatures flee the sphere: they ease out past the rim, where `lit`
// no longer holds — the crystal HERDS the wraiths, the gun kills them.
if (u[178] > 0.5) {
const dxu = w.x - u[179], dzu = w.z - u[181], du = Math.hypot(dxu, dzu) || 0.001
const rr = (u[182] || 0) + 1.0 // rim + FAT margin: the orbit spring fights the flee, equilibrium must land clearly outside `lit`
if (du < rr) {
const ease = Math.min(1, sdt * 12) // must OVERPOWER the 0.04/tick orbit spring or they equilibrate inside the sphere
w.x += (dxu / du) * (rr - du) * ease
w.z += (dzu / du) * (rr - du) * ease
w.x = Math.max(-4.2, Math.min(4.2, w.x)) // stay inside the pocket walls
w.z = Math.max(92.6, Math.min(101.4, w.z))
}
}
const lit = (u[178] > 0.5) && Math.hypot(w.x - u[179], w.z - u[181]) < (u[182] || 0)
// contact damage
if (T.grace <= 0 && Math.hypot(px - w.x, pz - w.z) < 0.9) { V.hp = Math.max(0, (V.hp != null ? V.hp : 1) - 0.14 * sdt * 10) }
// bolts kill ONLY when the wraith is in reality (outside the unlight) — the twist
if (!lit && Array.isArray(V.bolts)) for (const b of V.bolts) {
if (b.life > 0 && Math.hypot(b.x - w.x, b.z - w.z) < 0.8 && (b.y == null || (b.y > 0 && b.y < 2.4))) {
w.hp -= 0.34; b.life = 0; w.hurt = 1
if (w.hp <= 0) { w.dead = 1; if (wd.__play_sound == null) wd.__play_sound = [{ frequency: 260, duration: 0.3, volume: 0.24, type: 'sawtooth' }, { frequency: 1300, duration: 0.1, volume: 0.14, type: 'square' }] }
}
}
if (Array.isArray(V.pop)) {
const wy = Math.atan2(px - w.x, pz - w.z)
V.pop.push(w.x, 0, w.z, 13, 1, w.ph % 1000, Number.isFinite(wy) ? wy : 0, w.hurt ? 1 : ((lit || T.grace > 0) ? 0.35 : 0)) // lit = ghostly (invulnerable) tint on aux
w.hurt = 0
}
}
T.cleared = alive === 0 ? 1 : 0
// THE EXIT — only once all three are down
if (T.cleared && Array.isArray(V.pop)) {
if (globalThis.__VF_PORTAL) globalThis.__VF_PORTAL(V, C.x, C.z + 4.6, t)
if (Math.hypot(px - C.x, pz - (C.z + 4.6)) < 0.95) {
// land beside the LIVE sarcophagus — read the crypt wing from __IX like
// vf-crypt-room does (the old 1.6/-5.05 hardcode predated the wing's move
// to x~-7.3 and dumped you in the nave)
const _cb9 = (globalThis.__IX && globalThis.__IX.fields && globalThis.__IX.fields['crypt.bounds']) || { x: -7.3, z: -5.0, hx: 3.3 }
V.px = (_cb9.x - _cb9.hx / 3.0) + 1.6; V.pz = _cb9.z; V.py = 1.7; V.warp = 1
if (V.__sarco) { V.__sarco.inTomb = false; V.__sarco.open = 0 } // lid reseals
V.score = (V.score || 0) + 300
wd.__play_sound = [{ frequency: 880, duration: 0.25, volume: 0.2, type: 'sine' }, { frequency: 1320, duration: 0.2, volume: 0.12, type: 'triangle' }]
V.__tomb = { w: null, cleared: 0, t0: null }
}
}
}
} catch (e) {}
hook · vf-lurker-ball
by Claude (Fable)
// vf-lurker-ball (Galen) — give the Veil Lurker the SAME damage framework as demons.
// Demons live in V.en and take: the slot-4 BLADE sweep (reach 1.5, DPS 1.6) AND the
// slot-3 EMBER BALL. The lurker is a bespoke entity OUTSIDE V.en, so it missed both
// (it only had its own bolt check). This companion applies the exact demon sources to
// V.__lurker with demon-parity values. A blade/ball kill REWARDS (+150).
const wd = sim.worldData, V = wd && wd.__vf
if (V) {
const L = V.__lurker
const DT = (typeof dt === 'number' ? dt : (typeof sdt === 'number' ? sdt : 0.016))
if (L && L.woke && !L.dead) {
let hit = false
// BLADE (slot 4) — mirror the demon blade sweep exactly (V.blade, R9=1.5, DPS=1.6)
const B = V.blade
if (B && B.mode === 'out' && Math.hypot((B.x || 0) - L.x, (B.z || 0) - L.z) < 1.5) {
L.hp = Math.max(0, L.hp - 1.6 * DT); hit = true
}
// EMBER BALL (slot 3) — a moving/held ball on contact
const NC = globalThis.__VF_NCX
if (NC && (NC.free || NC.held)) {
const bsp = Math.hypot(NC.vx || 0, NC.vy || 0, NC.vz || 0)
if (Math.hypot((NC.bx || 0) - L.x, (NC.bz || 0) - L.z) < 1.5 && (NC.by || 0) > 0 && (NC.by || 0) < 2.6 && bsp > 5) {
L.hp = Math.max(0, L.hp - 0.5); hit = true
}
}
if (hit) {
L.hurt = 1
if (wd.__play_sound == null) wd.__play_sound = [{ frequency: 320, duration: 0.06, volume: 0.2, type: 'square' }]
if (L.hp <= 0) {
L.dead = 1; V.score = (V.score || 0) + 150
wd.__play_sound = [
{ frequency: 880, duration: 0.10, volume: 0.24, type: 'sine' },
{ frequency: 1320, duration: 0.14, volume: 0.20, type: 'sine' },
{ frequency: 1760, duration: 0.22, volume: 0.16, type: 'triangle' }]
}
}
}
}
hook · vf-blade-gate
by Claude (Fable)
// vf-blade-gate (Galen) — VISIBLE ember-blade pickup at the CITY GATE approach.
// A glinting sword (kind 11 = scatter blade) floats on the centre street just before
// the gate; walk into it to arm slot 4. This replaces the old silent grant AND the
// blade that was trapped in the secret backroom (z=13, gated behind __VF_SECRET0),
// which is why the pickup was never visible.
const wd = sim.worldData, V = wd && wd.__vf
if (V && !V.hasW4) {
const GX = 0, GZ = -88, GY = 1.05
// render the floating pickup so it is actually SEEN
if (Array.isArray(V.pop)) {
const bob = GY + Math.sin((V.t || 0) * 1.6) * 0.14
const spin = ((V.t || 0) * 0.9) % 1
V.pop.push(GX, bob, GZ, 11, spin, 0, 0, 0)
}
// walk into it -> arm slot 4 (do NOT force-select; press 4 to wield, so the
// ember ball in slot 3 is never yanked out of your hand at the gate)
const px = V.px || 0, pz = V.pz || 0
if (Math.hypot(px - GX, pz - GZ) < 1.6) {
V.hasW4 = 1
const B = V.blade || (V.blade = { have: 0, x: GX, y: GY, z: GZ, mode: 'seed', spin: 0 })
B.have = 1
if (B.mode === 'seed') B.mode = 'hover'
if (wd.__play_sound == null) wd.__play_sound = [
{ frequency: 1240, duration: 0.16, volume: 0.16, type: 'sine' },
{ frequency: 1860, duration: 0.22, volume: 0.1, type: 'sine' }]
}
}
hook · vf-weapon-select
by weapons-carve
;(() => {
// vf-weapon-select — THE WEAPON AUTHORITY (carved out Aug 9, Galen: "carve out
// weapons into their own node"). The SINGLE node that owns weapon selection —
// it reads the number keys, enforces which slots you actually own, and is the
// FINAL WORD each frame on V.weapon + u[69] (active) + u[70] (owned bitmask).
// Because it runs LAST and nothing else selects, the number keys can never
// flicker against a competing writer.
// slots: 1 LANTERN · 2 GUN · 3 EMBER BALL (own via V.hasBall) · 4 BLADE (V.hasW4)
// Event auto-wields (the blade pickup arms slot 4, the city hands you the ball)
// still set V.weapon directly on their one-shot; this node VALIDATES and
// PRESERVES that choice — it only overrides on a number press or when the held
// slot isn't owned.
try {
const wd = sim.worldData, V = wd.__vf, u = wd.gpuUniforms
if (!V || !Array.isArray(u) || u.length < 72) return
const own = [true, true, !!V.hasBall, !!V.hasW4, false] // slots 1..5
let mask = 0; for (let i = 0; i < 5; i++) if (own[i]) mask += (1 << i)
u[70] = mask
if (V.weapon == null) V.weapon = 2 // veilfire arms on the GUN
// number keys 1..5 — select ONLY a slot you own (edge-pressed this frame)
const pressed = (wd.input && wd.input.pressed) || {}
for (let n = 1; n <= 5; n++) if (pressed[String(n)] && own[n - 1]) V.weapon = n
// ownership guard: an out-of-range or un-owned slot falls back to the lantern
if (!(V.weapon >= 1 && V.weapon <= 5) || !own[V.weapon - 1]) V.weapon = 1
u[69] = V.weapon
} catch (e) {}
})()
hook · vf-freshreset
honor wd.__fresh (R-reset) → wipe __vf so R starts the game fresh
// vf-freshreset (Galen) — honor the engine's R-key reset signal. On R the engine
// sets wd.__fresh=true to mean "start the game fresh", but no veilfire hook ever
// checked it, so run-state (__vf: weapons, crystals, score, position) survived —
// and a background tab's 2s sync put it back. Wipe __vf to an empty object (truthy
// so the other hooks don't early-return on !__vf; they re-init to base), and consume
// the one-shot flag. Result: R = clear progress + fresh game.
const wd = sim.worldData;
if (wd && wd.__fresh) { wd.__vf = {}; delete wd.__fresh; }
hook · vf-eyeshot-temp
by claude-code
// DEFUSED (chair, Aug 24) — temp eye-photo state for THE WATCHING/ALARM; no-op. Delete from a live tab.hook · vf-alarmshot-temp
by claude-code
// DEFUSED (chair, Aug 24) — temp eye-photo state for THE WATCHING/ALARM; no-op. Delete from a live tab.hook · vf-gable-window
by claude-code
// vf-gable-window — THE GABLE WINDOW GRAB (Galen: "the crystal goes INTO the window
// at the end of the hall — the window grabs you"). The gable facade (cath.wgsl
// mod_cath_gab) carves a real WINDOW at avenue centre (|x|<0.75, sill y=5.92,
// world z≈-96) — but the flame's flight collision is 2D (walkable(x,z) ignores
// height), so a thrown crystal STICKS on the wall plane (z≈-94.6) and can never
// physically pass the sill; and no trigger ever consumed it (this world's only
// lantern-distance trigger was the den summit orb). This node IS the trigger:
// a flame thrown AT the window face counts as thrown THROUGH it. The window
// answers — it GRABS you: the coded reality-morph (__tpMorph, ix-tween) snatches
// you through the gable into THE WING tunnel — and the crystal returns to your
// hand (deployed=false), which also un-latches the always-on reveal machinery
// (the stuck-crystal lag at the gable: reveal light + ix field never shut off).
try {
const wd = sim.worldData, V = wd.__vf
if (V) {
const L = V.lantern
if (L && L.deployed) {
const lx = L.x || 0, ly = L.y || 0, lz = L.z || 0
// the window mouth: gable plane z -93.2..-97, centred x 0, sill-and-head band
if (lz < -93.2 && lz > -97.5 && Math.abs(lx) < 1.7 && ly > 4.4 && ly < 8.5) {
L.deployed = false; L.rad = 0 // the window TAKES the crystal — back to your hand
V.wingWarp = null
V.warp = 2 // through the gable = THE WING side
V.px = 4.012; V.py = 1.7; V.pz = -96.5 // the tunnel just past the gable (walkable under warp 2)
V.vy = 0
wd.__tpMorph = (wd.__tpMorph || 0) + 1 // THE GRAB — the reality-morph yanks you through
wd.__play_sound = [
{ frequency: 55, duration: 0.9, volume: 0.34, type: 'sawtooth' },
{ frequency: 660, duration: 0.22, volume: 0.2, type: 'sine' },
{ frequency: 1320, duration: 0.4, volume: 0.12, type: 'triangle' }]
}
}
}
} catch (e) {}
hook · vf-city-crucifix
by claude-code
;(() => {
// vf-city-crucifix — THE BURNING FIVE: the city's flying enemies are five
// giant flaming crucifixes (render kind 14, crux module) that hunt the player
// through the dome. Hunt → dive → rise state machine, separation so they fan
// out and surround. Killable by bolts (tall-capsule + crossbeam hit-test,
// 5 hits each); death = cross-shaped triple ember burst + 200 pts via the
// combat scorer. They swat for 0.16 hp on a connected dive. Firelight lanes
// u152-171 (x,y,z,intensity ×5) feed the megashader's world-staining halo.
// Replaces the retired lane-runner interceptors (vf-city-crossing keeps the
// ember ball + hud). State on V.crux/V.cruxOn — wiped by R-reset with __vf.
const __wd = sim.worldData
if (!globalThis.__VF_GEO || !__wd.__vf) return
const __C = globalThis.__VF_FNS = globalThis.__VF_FNS || {}
const __REV = 'crux1/' + globalThis.__VF_GEO_REV
if (__C['vf-city-crucifix@rev'] !== __REV) {
const CRUX_N = 5
const DOME_X = 0, DOME_Z = -118, DOME_R = 20
function cruxSpawn(i) {
const a = (i / CRUX_N) * Math.PI * 2 + 0.7
const r = 11 + (i % 3) * 3
return { x: DOME_X + Math.cos(a) * r, y: 5.5 + (i % 2) * 1.5, z: DOME_Z + Math.sin(a) * r,
hp: 1.0, hurt: 0, ph: i * 1.9, yaw: 0, atk: 0, st: 'hunt', stT: 0,
vx: 0, vy: 0, vz: 0, dcd: 2 + i * 0.8 }
}
function crux(sim, dt) {
const wd = sim.worldData
const V = wd.__vf
const u = wd.gpuUniforms
if (!u) return
const step = Math.min(dt, 1 / 30)
for (let i = 0; i < CRUX_N; i++) u[155 + i * 4] = 0 // lights off unless burning below
const px = V.px || 0, py = V.py != null ? V.py : 1.7, pz = V.pz || 0
const inCity = (globalThis.__VF_RELICS_ALL || 0) > 0.5 && pz < -99 && (V.warp || 0) >= 0 && ((u[43] || 0) < 1.5) // PERF: dome only — no cost in the approach corridor (28fps report)
if (!inCity) { V.cruxOn = 0; return }
if (!V.crux || !V.cruxOn) {
V.crux = []
for (let i = 0; i < CRUX_N; i++) V.crux.push(cruxSpawn(i))
V.cruxOn = 1
}
if (!Array.isArray(V.pop)) V.pop = []
if (!Array.isArray(V.deaths)) V.deaths = []
if (!Array.isArray(V.hits)) V.hits = []
for (const c of V.crux) {
if (c.hp <= 0) continue
c.ph += step * (1.2 + c.atk)
c.stT -= step; c.dcd -= step
c.hurt = Math.max(0, c.hurt - step * 3)
const dx = px - c.x, dz = pz - c.z
const distXZ = Math.hypot(dx, dz) || 1e-4
const ux = dx / distXZ, uz = dz / distXZ
let tx = ux, ty = 0, tz = uz, spd = 3.2
if (c.st === 'hunt') {
const orbit = Math.sin(c.ph * 0.7) * 0.8 // spiral in, don't beeline
tx = ux + -uz * orbit; tz = uz + ux * orbit
ty = (5.2 + Math.sin(c.ph) * 0.9 - c.y) * 0.6
c.atk = Math.max(0, c.atk - step * 2)
if (distXZ < 9 && c.dcd <= 0) { c.st = 'dive'; c.stT = 1.4; c.atk = 0 }
} else if (c.st === 'dive') {
c.atk = Math.min(1, c.atk + step * 3) // blaze up as it drops
const dy = (py + 0.4) - c.y
const d3 = Math.hypot(dx, dy, dz) || 1e-4
tx = dx / d3; ty = dy / d3; tz = dz / d3
spd = 8.5
if (d3 < 2.6) { // the burning arms swat you
V.hits.push({ dmg: 0.16, x: c.x, z: c.z })
c.st = 'rise'; c.stT = 1.2; c.dcd = 3.5 + Math.random() * 2.5
}
if (c.stT <= 0) { c.st = 'rise'; c.stT = 1.0; c.dcd = 2.5 + Math.random() * 2 }
} else { // rise — pull up and away
ty = 1.0; tx = -ux * 0.5; tz = -uz * 0.5; spd = 4.5
c.atk = Math.max(0, c.atk - step * 1.5)
if (c.stT <= 0) c.st = 'hunt'
}
for (const o of V.crux) { // separation: fan out, surround
if (o === c || o.hp <= 0) continue
const sx = c.x - o.x, sz = c.z - o.z
const sd = Math.hypot(sx, sz) || 1e-4
if (sd < 5) { tx += (sx / sd) * (5 - sd) * 0.35; tz += (sz / sd) * (5 - sd) * 0.35 }
}
const tl = Math.hypot(tx, ty, tz) || 1e-4
c.vx += (tx / tl * spd - c.vx) * Math.min(1, 2.2 * step)
c.vy += (ty / tl * spd - c.vy) * Math.min(1, 2.2 * step)
c.vz += (tz / tl * spd - c.vz) * Math.min(1, 2.2 * step)
c.x += c.vx * step; c.y += c.vy * step; c.z += c.vz * step
const rx = c.x - DOME_X, rz = c.z - DOME_Z // stay inside the dome shell
const rr = Math.hypot(rx, rz) || 1e-4
if (rr > DOME_R - 4.2) { c.x = DOME_X + rx / rr * (DOME_R - 4.2); c.z = DOME_Z + rz / rr * (DOME_R - 4.2) }
c.y = Math.max(3.4, Math.min(8.5, c.y))
let dyaw = Math.atan2(dx, dz) - c.yaw // face the prey, shortest arc
while (dyaw > Math.PI) dyaw -= Math.PI * 2
while (dyaw < -Math.PI) dyaw += Math.PI * 2
c.yaw += dyaw * Math.min(1, 4 * step)
}
// ---- damage: player bolts vs the burning five (1-frame lag, ambush law)
if (Array.isArray(V.bolts)) for (const b of V.bolts) {
if (!b || b.life <= 0) continue
for (const c of V.crux) {
if (c.hp <= 0) continue
const bx = b.x - c.x, bz = b.z - c.z
const beamY = Math.max(0, Math.abs(b.y - c.y) - 3.0) // tall vertical beam
const armY = Math.abs(b.y - (c.y + 1.35)) // crossbeam slab
const flat2 = bx * bx + bz * bz
if ((flat2 < 2.3 * 2.3 && armY < 0.9) || flat2 + beamY * beamY < 1.1 * 1.1) {
c.hp -= 0.2; if (c.hp <= 0.004) c.hp = 0; c.hurt = 1; b.life = 0; V.hitFlash = 0.55 // snap fp residue to a true kill
if (c.hp <= 0) {
V.deaths.push({ x: c.x, y: c.y, z: c.z }) // cross-shaped triple burst
V.deaths.push({ x: c.x - 1.4, y: c.y + 1.35, z: c.z })
V.deaths.push({ x: c.x + 1.4, y: c.y + 1.35, z: c.z })
V.ambKills = (V.ambKills || 0) + 2 // giant = 200 pts via combat
}
break
}
}
}
// ---- publish: kind-14 entities + ember wake + firelight lanes
let li = 0
for (const c of V.crux) {
if (c.hp <= 0) continue
V.pop.push(c.x, c.y, c.z, 14, Math.min(1, Math.max(0, c.hp)) + c.hurt, c.ph, c.yaw, Math.min(1, c.atk))
for (let k = 0; k < 3; k++) { // sparks shed off the burning arms
const eph = c.ph * 3.1 + k * 2.1
V.pop.push(c.x - c.vx * 0.12 * (k + 1) + Math.sin(eph) * 1.5,
c.y + 1.0 + Math.sin(eph * 1.7) * 1.6,
c.z - c.vz * 0.12 * (k + 1) + Math.cos(eph) * 1.5,
5, 0.35 + 0.3 * Math.sin(eph * 2.3), 0, 0, 0)
}
if (li < CRUX_N) {
u[152 + li * 4] = c.x; u[153 + li * 4] = c.y; u[154 + li * 4] = c.z
u[155 + li * 4] = 0.55 + 0.65 * c.atk + 0.3 * c.hurt
li++
}
}
}
__C['vf-city-crucifix'] = crux
__C['vf-city-crucifix@rev'] = __REV
}
try { __C['vf-city-crucifix'](sim, dt) } catch (e) {}
})()