Revolving Dors
a dark gallery where one wall projects revolving doors built out of sunlight
THE VISION
A one-room night gallery: charcoal walls, walnut floor, a single 3.1m framed projection on the north wall. The piece is ALIVE: three brass revolving doors (2.3m) backlit by a low sun, wings turning counter-clockwise, light refracting through the glass, fans of sun sweeping a dark reflective lobby floor, silhouetted people coming through. A projector cone of dust crosses the dark room; the piece reflects in the polished floor. Mood: hushed museum dark (meanLum ~12), one warm focal wall (#ffb870).
HOW TO PLAY
WASD / arrows - walk & turn SPACE - change the doors piece CLICK - play/stop the east-wall television (walk up to it) A night gallery around a revolving door. Each wall holds a living piece; the east wall has a TV - stand close, face it, and click to play the clip. Snowmen grow in the SNOWFIELD, fly out of its frame, and dance under the disco ball in the south-east corner.
— VISUAL SHADERS (WGSL) —
visual · rd_view
fn visual_rd_view(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
let ro = uni4(60).xyz;
let rd = mod_w3_ray(uv, ro, uni4(61).xyz, max(uni4(60).w, 0.6));
var col = vec3f(0.004, 0.004, 0.006);
let hit = mod_rg_march(ro, rd);
var tScene = 30.0;
if (hit.x > 0.0) {
tScene = hit.x;
let pos = ro + rd * hit.x;
let n = mod_rg_nrm(pos);
let m = i32(hit.y);
let piece = mod_rg_piece(pos, n, time);
if (piece.w > 0.5) {
// ── a living projection — plus the screen's glass sheen
col = piece.rgb + vec3f(0.9, 0.92, 0.9) * mod_w3_fresnel(n, rd, 0.03) * 0.055;
} else {
col = mod_rg_shade(pos, n, m, time);
if (m == 20) {
// the polished floor carries the piece — reflect and look again
let rr = reflect(rd, n);
let rh = mod_rg_march(pos + n * 0.02, rr);
if (rh.x > 0.0) {
let rpos = pos + n * 0.02 + rr * rh.x;
let rn = mod_rg_nrm(rpos);
var rcol = mod_rg_shade(rpos, rn, i32(rh.y), time);
let rpiece = mod_rg_pieceR(rpos, rn, time);
if (rpiece.w > 0.5) { rcol = rpiece.rgb * 0.88; }
// the dancers and the ball show in the polish too
let smr = mod_sm_trace(pos + n * 0.02, rr, rh.x, time);
if (smr.w > 0.0) { rcol = smr.rgb * 0.85; }
let fr = mod_w3_fresnel(n, rd, 0.035);
col += rcol * fr * 0.9;
}
}
}
}
// ── the visitors: cel snowmen + the disco ball, depth-composited ──
let sm = mod_sm_trace(ro, rd, tScene, time);
if (sm.w > 0.0) { col = sm.rgb; tScene = sm.w; }
// ── the projector beam: a dusty cone from the lens to the wall ──
let A = vec3f(0.0, 3.78, -4.1); // lens
let B = vec3f(0.0, 2.0, 4.78); // screen center
let AB = B - A; let abLen = length(AB); let abDir = AB / abLen;
var acc = 0.0;
for (var k = 0; k < 10; k++) {
let t = tScene * (f32(k) + 0.5 + 0.35 * hash21(uv * 77.3 + f32(k))) / 10.0;
let ps = ro + rd * t;
let s = clamp(dot(ps - A, abDir), 0.0, abLen);
let axDist = length(ps - (A + abDir * s));
let coneR = 0.10 + s * 0.19;
let inCone = smoothstep(coneR, coneR * 0.55, axDist);
if (inCone < 0.01) { continue; }
let dust = 0.6 + 0.4 * vnoise3(ps * 2.2 + vec3f(0.0, time * 0.06, 0.0));
acc += inCone * dust;
}
col += vec3f(1.0, 0.82, 0.58) * min(acc, 6.0) * (tScene / 10.0) * 0.050;
return vec4f(col, 1.0);
}
visual · base_bg
fn visual_base_bg(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
let g = uv.y * 0.5 + 0.5;
var col = mix(vec3f(0.075, 0.085, 0.115), vec3f(0.028, 0.033, 0.05), g);
col += vec3f(0.05, 0.06, 0.09) * (0.5 + 0.5 * sin(uv.x * 2.0 + uv.y * 3.0)) * 0.06;
col += 0.010 * (vnoise(uv * 34.0) - 0.5);
// a soft floor line so the canvas reads as a PLACE, not a void
let horizon = smoothstep(0.02, 0.0, abs(uv.y - 0.35));
col += vec3f(0.10, 0.13, 0.18) * horizon * 0.25;
return vec4f(col, 1.0);
}visual · rd_piece_rt
// THE PIECE, rendered once per frame into the rd_piece target. The wall and
// the floor reflection sample this instead of re-marching the doors scene —
// the raymarch-inside-a-raymarch this world could not afford.
fn visual_rd_piece_rt(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
return vec4f(mod_rd_image(uv, time), 1.0);
}
— SHADER MODULES —
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 {
var t = tmin;
for (var i = 0; i < 256; i++) {
if (i >= steps) { break; }
let dm = w3_map(ro + rd * t);
if (dm.x < 0.001 * t + 0.003) { return vec2f(t, dm.y); }
t = t + max(dm.x * 0.9, 0.004);
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)
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 < 24; i++) {
let d = w3_map(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
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(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));
}
module · rd_scene
// REVOLVING DORS — scene SDF + the light. Three revolving doors (2.3m ≈ 7.5ft)
// in the north wall of a dark lobby, backlit by a low sun. The wings turn
// counter-clockwise and the LIGHT is computed as light (an analytic gate),
// not as an afterthought of geometry — so the doors can be removed and the
// light remains. uni map: 40..42 wing angles · 50 mode (0 doors, 1 removed,
// 2 doors of pure light) · 52+ people (x, z, phase, active) x3.
// distance to a capped-cylinder REGION (the honest early-out bound —
// an infinite cylinder bound renders phantom towers above everything)
fn mod_rd_bound(q: vec3f, R: f32, cy: f32, hy: f32) -> f32 {
let dxz = length(q.xz) - R;
let dy = abs(q.y - cy) - hy;
return length(vec2f(max(dxz, 0.0), max(dy, 0.0)));
}
fn mod_rd_toSun() -> vec3f { return normalize(vec3f(0.20, 0.315, 0.93)); } // 18° elevation — ~7m throw
fn mod_rd_doorx(i: i32) -> f32 { return f32(i) * 5.0 - 5.0; }
fn mod_rd_suncol() -> vec3f { return vec3f(1.0, 0.80, 0.55) * 9.0; }
fn mod_rd_skycol() -> vec3f { return vec3f(0.16, 0.18, 0.24); }
// ── the wings, as photographed (195 Broadway / US National Bank refs):
// framed glass — brass kick plate below, push-bar rails at hand height,
// frame edges — never a bare sheet. Returns (glass d, frame d).
fn mod_rd_wingGlass(q: vec3f) -> f32 {
// crossed glass panes, upper part only (kick plate owns the bottom 0.35m)
let g1 = mod_w3_box(vec3f(q.x, q.y - 0.17, q.z), vec3f(1.0, 0.96, 0.012));
let g2 = mod_w3_box(vec3f(q.x, q.y - 0.17, q.z), vec3f(0.012, 0.96, 1.0));
return min(g1, g2);
}
fn mod_rd_wingFrame(q: vec3f) -> f32 {
// kick plates: solid lower band of each wing
let k1 = mod_w3_box(vec3f(q.x, q.y + 0.975, q.z), vec3f(1.0, 0.175, 0.028));
let k2 = mod_w3_box(vec3f(q.x, q.y + 0.975, q.z), vec3f(0.028, 0.175, 1.0));
var d = min(k1, k2);
// top rails
let t1 = mod_w3_box(vec3f(q.x, q.y - 1.10, q.z), vec3f(1.0, 0.035, 0.026));
let t2 = mod_w3_box(vec3f(q.x, q.y - 1.10, q.z), vec3f(0.026, 0.035, 1.0));
d = min(d, min(t1, t2));
// outer stiles (vertical edge bars at the wing tips)
let s1 = mod_w3_box(vec3f(abs(q.x) - 0.99, q.y, q.z), vec3f(0.025, 1.13, 0.026));
let s2 = mod_w3_box(vec3f(q.x, q.y, abs(q.z) - 0.99), vec3f(0.026, 1.13, 0.025));
d = min(d, min(s1, s2));
// push bars — two rails at hand height on each wing arm
let ay = abs(q.y + 0.12) - 0.065; // rails at y≈1.09 and 1.22 (pre-shift)
let b1 = max(mod_w3_capsule(q, vec3f(0.3, -0.12, 0.0), vec3f(0.92, -0.12, 0.0), 0.016), ay);
let b2 = max(mod_w3_capsule(q, vec3f(-0.92, -0.12, 0.0), vec3f(-0.3, -0.12, 0.0), 0.016), ay);
let b3 = max(mod_w3_capsule(q, vec3f(0.0, -0.12, 0.3), vec3f(0.0, -0.12, 0.92), 0.016), ay);
let b4 = max(mod_w3_capsule(q, vec3f(0.0, -0.12, -0.92), vec3f(0.0, -0.12, -0.3), 0.016), ay);
d = min(d, min(min(b1, b2), min(b3, b4)));
return d;
}
// drum: curved glass side arcs + solid brass canopy band above
fn mod_rd_drumGlass(q: vec3f) -> f32 {
var d = abs(length(q.xz) - 1.10) - 0.018;
d = max(d, 0.62 - abs(q.x)); // keep only the side arcs
d = max(d, abs(q.y - 1.15) - 1.15); // height clamp
return d;
}
fn mod_rd_canopy(q: vec3f) -> f32 {
// the solid ring band that crowns every real drum (~0.3m tall)
var d = abs(length(q.xz) - 1.13) - 0.07;
d = max(d, abs(q.y - 2.46) - 0.16);
return d;
}
// a walker built as a FIGURE (classical canon ~7.5 heads): torso taper,
// swinging bezier legs with knees, counter-phase arms with elbows.
fn mod_rd_person(q0: vec3f, phase: f32) -> f32 {
let bob = 0.035 * abs(cos(phase));
let q = vec3f(q0.x, q0.y - bob, q0.z);
let stride = sin(phase) * 0.30;
let head = mod_w3_sphere(q - vec3f(0.0, 1.60, 0.0), 0.105);
let neck = mod_w3_capsule(q, vec3f(0.0, 1.44, 0.0), vec3f(0.0, 1.52, 0.0), 0.045);
let torso = mod_w3_taperStrut(q, vec3f(0.0, 1.42, 0.0), vec3f(0.0, 0.96, 0.0), 0.15, 0.115);
var d = opSmoothUnion(opSmoothUnion(head, neck, 0.04), torso, 0.06);
// legs: hip → foot, knee bowed forward through the bezier control
let lgL = mod_w3_bezStrut(q, vec3f(0.09, 0.92, 0.0), vec3f(0.09, 0.48, stride * 0.5 + 0.06), vec3f(0.09, 0.04, stride), 0.055);
let lgR = mod_w3_bezStrut(q, vec3f(-0.09, 0.92, 0.0), vec3f(-0.09, 0.48, -stride * 0.5 + 0.06), vec3f(-0.09, 0.04, -stride), 0.055);
d = opSmoothUnion(d, min(lgL, lgR), 0.05);
// arms: counter-phase, elbow trailing
let amL = mod_w3_bezStrut(q, vec3f(0.19, 1.38, 0.0), vec3f(0.22, 1.10, -stride * 0.4 - 0.03), vec3f(0.22, 0.84, -stride * 0.7), 0.038);
let amR = mod_w3_bezStrut(q, vec3f(-0.19, 1.38, 0.0), vec3f(-0.22, 1.10, stride * 0.4 - 0.03), vec3f(-0.22, 0.84, stride * 0.7), 0.038);
d = opSmoothUnion(d, min(amL, amR), 0.04);
return d;
}
fn w3_map(p: vec3f) -> vec2f {
var d = p.y; var m = 1.0; // the floor owns by default
// north wall, pierced by three door openings, with jambs + header trim
let wq = vec3f(p.x, p.y - 1.8, p.z - 8.2);
var wall = mod_w3_box(wq, vec3f(12.0, 1.8, 0.22));
for (var i = 0; i < 3; i++) {
let dx = mod_rd_doorx(i);
wall = max(wall, -mod_w3_box(vec3f(p.x - dx, p.y - 1.2, p.z - 8.2), vec3f(1.12, 1.2, 0.6)));
}
if (wall < d) { d = wall; m = 2.0; }
let mode = uni(50);
for (var i = 0; i < 3; i++) {
let dx = mod_rd_doorx(i);
let q = vec3f(p.x - dx, p.y, p.z - 8.0);
let g = mod_rd_bound(q, 1.45, 1.4, 1.45); // capped bound around the whole door
if (g > 0.001) { if (g < d) { d = g; m = 2.0; } continue; }
// portal trim survives every mode (it is the wall's, not the door's)
let j1 = mod_w3_box(vec3f(abs(q.x) - 1.19, q.y - 1.31, q.z), vec3f(0.09, 1.31, 0.30));
let hd = mod_w3_box(vec3f(q.x, q.y - 2.60, q.z), vec3f(1.28, 0.20, 0.30));
let tr = min(j1, hd);
if (tr < d) { d = tr; m = 7.0; }
if (mode < 0.5) { // the door itself exists only in mode 0
let a = uni(40 + i);
let qa = mod_w3_rotY(vec3f(q.x, q.y - 1.15, q.z), a);
let wgl = mod_rd_wingGlass(qa);
if (wgl < d) { d = wgl; m = 3.0; }
let wfr = mod_rd_wingFrame(qa);
if (wfr < d) { d = wfr; m = 4.0; }
let dr = mod_rd_drumGlass(q);
if (dr < d) { d = dr; m = 6.0; }
let cn = mod_rd_canopy(q);
let pl = mod_w3_cyl(vec3f(q.x, q.y - 1.15, q.z), 1.15, 0.05);
let th = mod_w3_cyl(vec3f(q.x, q.y - 0.012, q.z), 0.012, 1.16); // threshold plate
let mt = min(min(cn, pl), th);
if (mt < d) { d = mt; m = 4.0; }
}
}
// the people come through in every mode
for (var j = 0; j < 3; j++) {
if (uni(73 + j * 4) > 0.5) {
let q = vec3f(p.x - uni(70 + j * 4), p.y, p.z - uni(71 + j * 4));
let g = mod_rd_bound(q, 0.55, 0.9, 0.95);
if (g > 0.001) { if (g < d) { d = g; m = 5.0; } continue; }
let pe = mod_rd_person(q, uni(72 + j * 4));
if (pe < d) { d = pe; m = 5.0; }
}
}
return vec2f(d, m);
}
// ── THE LIGHT, DONE AS LIGHT ────────────────────────────────────────────────
// Sun visibility at any interior point: project along the sun to the wall
// plane; pass only through an opening; in modes 0 and 2 a rotating four-blade
// gate (the wings, as pure light) modulates the pass. The 20ft floor spill is
// free: opening height 2.4m / sun elevation ≈ 23° → ~5.7m throw.
fn mod_rd_gate(p: vec3f) -> f32 {
if (p.z > 8.05) { return 1.0; } // outside: full sun
let toSun = mod_rd_toSun();
let tw = (8.0 - p.z) / toSun.z;
if (tw < 0.0) { return 0.0; }
let q = p + toSun * tw; // crossing point on the wall plane
let mode = uni(50);
var g = 0.0;
for (var i = 0; i < 3; i++) {
let dx = mod_rd_doorx(i);
let op = smoothstep(1.10, 0.94, abs(q.x - dx)) *
smoothstep(2.42, 2.25, q.y) * smoothstep(-0.02, 0.06, q.y);
if (op <= 0.001) { continue; }
var fan = 1.0;
let lp = vec2f(p.x - dx, p.z - 8.0);
if (mode < 0.5 || mode > 1.5) {
let phi = atan2(lp.y, lp.x);
let s = fract((phi + uni(40 + i)) * 0.63662); // /(π/2): the 4-wing gate
fan = smoothstep(0.02, 0.58, min(s, 1.0 - s)); // soft blades — light, not paint
// caustic shimmer: the turning panes wrinkle their own light
let a = uni(40 + i);
fan *= 0.62 + 0.55 * vnoise(vec2f(phi * 4.0 + a * 3.0, length(lp) * 1.1 - a * 2.0));
}
// shine feathers out from the door mouth, brightest where it pours in
let r = length(lp);
let pour = 0.30 + 0.85 * exp(-r * 0.11);
g = max(g, op * fan * pour);
}
return g;
}
fn mod_rd_sky(rd: vec3f) -> vec3f {
let toSun = mod_rd_toSun();
var col = mix(vec3f(0.045, 0.042, 0.048), vec3f(0.010, 0.012, 0.020),
smoothstep(-0.05, 0.35, rd.y));
let s = max(dot(rd, toSun), 0.0);
let horizBand = exp(-abs(rd.y - 0.12) * 6.0);
col += vec3f(1.0, 0.62, 0.30) * pow(s, 10.0) * horizBand * 1.1;
col += vec3f(1.0, 0.72, 0.42) * pow(s, 24.0) * 1.2;
col += vec3f(5.0, 3.8, 2.5) * smoothstep(0.9994, 0.9999, dot(rd, toSun));
return col;
}
fn mod_rd_albedo(m: i32, p: vec3f) -> vec3f {
if (m == 1) { // polished dark stone in, pale pavement out
let mottle = 0.7 + 0.3 * vnoise(p.xz * 0.6);
let outside = smoothstep(8.0, 8.6, p.z);
return mix(vec3f(0.14, 0.14, 0.15), vec3f(0.15, 0.14, 0.125), outside) * mottle;
}
if (m == 2) { return vec3f(0.30, 0.275, 0.23); } // warm limestone
if (m == 3) { return vec3f(0.06, 0.09, 0.085); } // wing glass
if (m == 4) { return vec3f(0.155, 0.105, 0.045); } // brass frames
if (m == 5) { return vec3f(0.030, 0.028, 0.030); } // a person, backlit
if (m == 7) { return vec3f(0.19, 0.145, 0.085); } // bronze portal trim
return vec3f(0.05, 0.07, 0.07); // drum glass
}
// one-bounce shade for reflections: albedo under gate light, no secondary rays
fn mod_rd_cheapShade(pos: vec3f, n: vec3f, m: i32) -> vec3f {
let toSun = mod_rd_toSun();
let dif = clamp(dot(n, toSun), 0.0, 1.0);
let g = mod_rd_gate(pos);
return mod_rd_albedo(m, pos) * (mod_rd_suncol() * dif * g + mod_rd_skycol() * (0.5 + 0.5 * n.y));
}
// ── opaque-only shadow: glass (wings, drum) passes light — only frames, wall,
// people and trim block. The fan gate IS the wings' light; geometry must not
// double-occlude it or the pools die (the first probe's lesson).
fn mod_rd_mapO(p: vec3f) -> f32 {
var d = p.y;
let wq = vec3f(p.x, p.y - 1.8, p.z - 8.2);
var wall = mod_w3_box(wq, vec3f(12.0, 1.8, 0.22));
for (var i = 0; i < 3; i++) {
wall = max(wall, -mod_w3_box(vec3f(p.x - mod_rd_doorx(i), p.y - 1.2, p.z - 8.2), vec3f(1.12, 1.2, 0.6)));
}
d = min(d, wall);
let mode = uni(50);
for (var i = 0; i < 3; i++) {
let q = vec3f(p.x - mod_rd_doorx(i), p.y, p.z - 8.0);
let g = mod_rd_bound(q, 1.45, 1.4, 1.45);
if (g > 0.001) { d = min(d, g); continue; }
d = min(d, min(
mod_w3_box(vec3f(abs(q.x) - 1.19, q.y - 1.31, q.z), vec3f(0.09, 1.31, 0.30)),
mod_w3_box(vec3f(q.x, q.y - 2.60, q.z), vec3f(1.28, 0.20, 0.30))));
if (mode < 0.5) {
let qa = mod_w3_rotY(vec3f(q.x, q.y - 1.15, q.z), uni(40 + i));
d = min(d, mod_rd_wingFrame(qa));
d = min(d, min(mod_rd_canopy(q), mod_w3_cyl(vec3f(q.x, q.y - 1.15, q.z), 1.15, 0.05)));
}
}
for (var j = 0; j < 3; j++) {
if (uni(73 + j * 4) > 0.5) {
let q = vec3f(p.x - uni(70 + j * 4), p.y, p.z - uni(71 + j * 4));
let g = mod_rd_bound(q, 0.55, 0.9, 0.95);
if (g > 0.001) { d = min(d, g); continue; }
d = min(d, mod_rd_person(q, uni(72 + j * 4)));
}
}
return d;
}
fn mod_rd_shadowO(p: vec3f, ld: vec3f) -> f32 {
var res = 1.0; var t = 0.06;
for (var i = 0; i < 14; i++) {
let h = mod_rd_mapO(p + ld * t);
if (h < 0.001) { return 0.0; }
res = min(res, 9.0 * h / t);
t += clamp(h, 0.04, 1.2);
if (t > 16.0) { break; }
}
return clamp(res, 0.0, 1.0);
}
// smooth bounce field — the warm breath of the pools, fanless (the projected
// fan painted swirls on the walls; ambience must be soft)
fn mod_rd_glow(p: vec3f) -> f32 {
var g = 0.0;
for (var i = 0; i < 3; i++) {
let lp = vec3f(p.x - mod_rd_doorx(i), p.y * 0.6, p.z - 8.0);
g += exp(-length(lp) * 0.22);
}
return g;
}
// ── THE PIECE — the doors scene rendered from its fixed vantage;
// what the projector throws on the gallery wall. Alive: wings turn,
// people come through, the mode experiment plays inside it.
fn mod_rd_image(uv: vec2f, time: f32) -> vec3f {
let ro = vec3f(0.0, 1.65, 0.5);
let rd = mod_w3_ray(uv, ro, vec3f(0.0, 1.58, 3.5), 1.1);
let toSun = mod_rd_toSun();
let sunCol = mod_rd_suncol();
let skyCol = mod_rd_skycol();
var col = mod_rd_sky(rd);
let hit = mod_w3_march(ro, rd, 0.05, 35.0, 56);
var tScene = 35.0;
if (hit.x > 0.0) {
tScene = hit.x;
let pos = ro + rd * hit.x;
let m = i32(hit.y);
var n = mod_w3_nrm(pos, 0.02);
let g = mod_rd_gate(pos);
var sh = 1.0;
if (g > 0.01) { sh = mod_rd_shadowO(pos + n * 0.05, toSun); } // frames+people carve; glass passes
let ao = mod_w3_ao(pos, n);
let alb = mod_rd_albedo(m, pos);
let dif = clamp(dot(n, toSun), 0.0, 1.0);
// base: gated key + dim sky fill + warm floor bounce
col = alb * (sunCol * dif * g * sh + skyCol * (0.5 + 0.5 * n.y) * ao);
// bounce: the pools breathe warmth into the room — smooth, fanless
let bounce = mod_rd_glow(pos);
col += alb * vec3f(1.0, 0.75, 0.5) * (0.10 + 0.50 * bounce) * ao * (0.5 + 0.5 * clamp(-n.z, 0.0, 1.0));
col += alb * vec3f(0.9, 0.7, 0.5) * clamp(-n.y, 0.0, 1.0) * 0.25 * ao;
if (m == 1) { // ── the reflective floor
let rough = vnoise(pos.xz * 3.1) * 0.014;
let nn = normalize(vec3f(n.x + rough, n.y, n.z - rough));
let rr = reflect(rd, nn);
var rcol = mod_rd_sky(rr);
// the doors' mirror image AS LIGHT — glowing mouths on the wall plane,
// wing-gated. No second march: the light is the picture down here.
if (rr.z > 0.001) {
let tw = (8.0 - pos.z) / rr.z;
let q = pos + rr * tw;
var open = 0.0;
for (var i = 0; i < 3; i++) {
let dx = mod_rd_doorx(i);
open = max(open, smoothstep(1.12, 0.9, abs(q.x - dx)) *
smoothstep(2.45, 2.2, q.y) * smoothstep(-0.02, 0.06, q.y));
}
// solid wall blocks the reflected sky (and its sun) — only the mouths pass
let wallHit = step(abs(q.x), 12.0) * smoothstep(-0.2, 0.0, q.y) * (1.0 - smoothstep(3.6, 3.8, q.y));
rcol = mix(rcol, vec3f(0.030, 0.024, 0.020), wallHit * (1.0 - open));
rcol += mod_rd_suncol() * open * mod_rd_gate(vec3f(q.x, q.y, 7.7)) * 0.55;
}
let fr = mod_w3_fresnel(nn, rd, 0.045);
col += rcol * fr * exp(-hit.x * 0.02);
// the sun streak in the polish — a tight core in a broad sheen
let rs = clamp(dot(rr, toSun), 0.0, 1.0);
col += sunCol * (pow(rs, 500.0) * 2.0 + pow(rs, 40.0) * 0.35) * g * sh;
}
if (m == 3 || m == 6) { // ── glass REFRACTS: bend, ripple, march through
let eta = 1.0 / 1.12;
var rf = refract(rd, n, eta);
if (dot(rf, rf) < 0.25) { rf = reflect(rd, n); } // total internal reflection
let rip = vnoise(pos.xy * 6.0 + vec2f(pos.z * 2.5, time * 0.05)) - 0.5;
rf = normalize(rf + vec3f(rip * 0.09, rip * 0.05, 0.0)); // the pane's waviness
let th = mod_w3_march(pos + rf * 0.06, rf, 0.02, 40.0, 32);
var tcol = mod_rd_sky(rf);
if (th.x > 0.0) {
let tpos = pos + rf * (0.06 + th.x);
tcol = mod_rd_cheapShade(tpos, mod_w3_nrm(tpos, 0.03), i32(th.y));
}
let fr = mod_w3_fresnel(n, rd, 0.05);
col = tcol * vec3f(0.80, 0.88, 0.84) * (1.0 - fr) + mod_rd_sky(reflect(rd, n)) * fr;
col += sunCol * pow(clamp(dot(reflect(rd, n), toSun), 0.0, 1.0), 300.0) * 1.2;
}
if (m == 4 || m == 7) { // brass/bronze spec
let hal = normalize(toSun - rd);
col += sunCol * pow(clamp(dot(n, hal), 0.0, 1.0), 90.0) * g * 0.8;
}
if (m == 5) { // rim on the silhouettes
let rim = pow(1.0 - clamp(dot(n, -rd), 0.0, 1.0), 3.0);
col += vec3f(1.0, 0.8, 0.58) * rim * clamp(dot(rd, toSun) * 0.5 + 0.5, 0.0, 1.0) * 0.9;
}
col = mod_w3_fog(col, mod_rd_sky(rd), hit.x, 0.0007);
}
// ── volumetric shafts: the air remembers the doors ──
let vt = min(tScene, 26.0);
var acc = 0.0;
for (var k = 0; k < 12; k++) {
let t = vt * (f32(k) + 0.5 + 0.35 * hash21(uv * 91.7 + f32(k))) / 12.0;
let ps = ro + rd * t;
// every falloff SMOOTH — hard bounds painted a stripe across the frame
let hfade = exp(-max(ps.y - 2.3, 0.0) * 2.2) * smoothstep(8.4, 7.6, ps.z);
if (hfade < 0.01) { continue; }
let dust = 0.72 + 0.28 * vnoise3(ps * 1.6 + vec3f(0.0, 0.0, time * 0.12));
acc += mod_rd_gate(ps) * dust * hfade;
}
col += vec3f(1.0, 0.78, 0.52) * min(acc, 5.0) * (vt / 12.0) * 0.030; // shaft-parallel views must not blow out
return col;
}
// ── THE GALLERY (mod_rg_*) — charcoal room, walnut floor, one lit wall.
// Screen: north wall, 3.9m square centered x=0, y 0.25..4.15.
fn mod_rg_map(p: vec3f) -> vec2f {
var d = p.y; var m = 20.0; // floor
let ceil = 4.2 - p.y;
if (ceil < d) { d = ceil; m = 21.0; }
let wx = 7.8 - abs(p.x);
if (wx < d) { d = wx; m = 21.0; }
let wz = 4.8 - abs(p.z);
if (wz < d) { d = wz; m = 21.0; } // north face carries the screen (by position)
// frame molding around the piece
let fq = vec3f(p.x, p.y - 2.0, p.z - 4.78);
let frame = max(mod_w3_box(fq, vec3f(1.70, 1.70, 0.06)), -mod_w3_box(fq, vec3f(1.55, 1.55, 0.3)));
if (frame < d) { d = frame; m = 24.0; }
// the second frame, east wall
let f2 = vec3f(p.x - 7.76, p.y - 2.0, p.z);
let frame2 = max(mod_w3_box(f2, vec3f(0.06, 1.45, 1.45)), -mod_w3_box(f2, vec3f(0.3, 1.30, 1.30)));
if (frame2 < d) { d = frame2; m = 24.0; }
// west frame (THE PLAN OF LIGHT)
let f3 = vec3f(p.x + 7.76, p.y - 2.0, p.z);
let frame3 = max(mod_w3_box(f3, vec3f(0.06, 1.45, 1.45)), -mod_w3_box(f3, vec3f(0.3, 1.30, 1.30)));
if (frame3 < d) { d = frame3; m = 24.0; }
// south pair (NIGHT RAIN · POOLLIGHT)
let f4 = vec3f(abs(p.x) - 4.0, p.y - 2.0, p.z + 4.76);
let frame4 = max(mod_w3_box(f4, vec3f(1.20, 1.20, 0.06)), -mod_w3_box(f4, vec3f(1.05, 1.05, 0.3)));
if (frame4 < d) { d = frame4; m = 24.0; }
// east TV — the interactive television (its own deeper bezel), z=-2.6
let f5 = vec3f(p.x - 7.76, p.y - 1.95, p.z + 2.6);
let frame5 = max(mod_w3_box(f5, vec3f(0.12, 1.02, 1.32)), -mod_w3_box(f5, vec3f(0.3, 0.86, 1.15)));
if (frame5 < d) { d = frame5; m = 24.0; }
// west second frame — SNOWFIELD (landscape panel), z=+2.85
let f6 = vec3f(p.x + 7.76, p.y - 2.0, p.z - 2.85);
let frame6 = max(mod_w3_box(f6, vec3f(0.06, 1.00, 1.25)), -mod_w3_box(f6, vec3f(0.3, 0.84, 1.09)));
if (frame6 < d) { d = frame6; m = 24.0; }
// placards — the little cards beside every piece
var plq = mod_w3_box(vec3f(p.x - 2.35, p.y - 1.35, p.z - 4.79), vec3f(0.10, 0.065, 0.012));
plq = min(plq, mod_w3_box(vec3f(p.x - 7.79, p.y - 1.35, p.z - 1.85), vec3f(0.012, 0.065, 0.10)));
plq = min(plq, mod_w3_box(vec3f(p.x + 7.79, p.y - 1.35, p.z + 1.85), vec3f(0.012, 0.065, 0.10)));
plq = min(plq, mod_w3_box(vec3f(abs(p.x) - 5.45, p.y - 1.35, p.z + 4.79), vec3f(0.10, 0.065, 0.012)));
plq = min(plq, mod_w3_box(vec3f(p.x + 7.79, p.y - 1.35, p.z - 4.35), vec3f(0.012, 0.065, 0.10)));
if (plq < d) { d = plq; m = 25.0; }
// track lights — small cans on the ceiling before each wall
var trk = mod_w3_cyl(vec3f(p.x, p.y - 4.12, p.z - 3.6), 0.10, 0.055);
trk = min(trk, mod_w3_cyl(vec3f(p.x - 6.6, p.y - 4.12, p.z), 0.10, 0.055));
trk = min(trk, mod_w3_cyl(vec3f(p.x + 6.6, p.y - 4.12, p.z), 0.10, 0.055));
trk = min(trk, mod_w3_cyl(vec3f(abs(p.x) - 4.0, p.y - 4.12, p.z + 3.6), 0.10, 0.055));
trk = min(trk, mod_w3_cyl(vec3f(p.x + 6.6, p.y - 4.12, p.z - 2.85), 0.10, 0.055));
if (trk < d) { d = trk; m = 24.0; }
// one bench, mid-room
let bench = mod_w3_rbox(vec3f(p.x, p.y - 0.24, p.z + 0.8), vec3f(1.4, 0.22, 0.42), 0.04);
if (bench < d) { d = bench; m = 23.0; }
// the projector, hung from the ceiling behind the viewer
let pj = min(mod_w3_box(vec3f(p.x, p.y - 3.82, p.z + 4.3), vec3f(0.16, 0.10, 0.22)),
mod_w3_cyl(vec3f(p.x, p.y - 4.05, p.z + 4.3), 0.14, 0.02));
if (pj < d) { d = pj; m = 24.0; }
return vec2f(d, m);
}
fn mod_rg_march(ro: vec3f, rd: vec3f) -> vec2f {
var t = 0.02;
for (var i = 0; i < 44; i++) {
let h = mod_rg_map(ro + rd * t);
if (h.x < 0.002 + 0.001 * t) { return vec2f(t, h.y); }
t += max(h.x, 0.004);
if (t > 20.0) { break; }
}
return vec2f(-1.0, -1.0);
}
fn mod_rg_nrm(p: vec3f) -> vec3f {
let e = 0.015;
return normalize(vec3f(
mod_rg_map(p + vec3f(e, 0.0, 0.0)).x - mod_rg_map(p - vec3f(e, 0.0, 0.0)).x,
mod_rg_map(p + vec3f(0.0, e, 0.0)).x - mod_rg_map(p - vec3f(0.0, e, 0.0)).x,
mod_rg_map(p + vec3f(0.0, 0.0, e)).x - mod_rg_map(p - vec3f(0.0, 0.0, e)).x));
}
// is this wall position on the piece? → its -1..1 uv
fn mod_rg_screenUv(pos: vec3f) -> vec2f {
return vec2f(pos.x / 1.55, -(pos.y - 2.0) / 1.55);
}
fn mod_rg_onScreen(pos: vec3f, n: vec3f) -> bool {
let uv = mod_rg_screenUv(pos);
return pos.z > 4.7 && n.z < -0.5 && abs(uv.x) < 1.0 && abs(uv.y) < 1.0;
}
// gallery shading for one hit (no screen sampling here — callers do that)
fn mod_rg_shade(pos: vec3f, n: vec3f, m: i32, time: f32) -> vec3f {
var alb = vec3f(0.055);
if (m == 20) { // walnut planks
let plank = 0.75 + 0.25 * vnoise(vec2f(pos.x * 0.7, pos.z * 4.0));
alb = vec3f(0.075, 0.058, 0.045) * plank;
}
if (m == 21) { alb = vec3f(0.052, 0.050, 0.055); }
if (m == 23) { alb = vec3f(0.085, 0.075, 0.065); }
if (m == 24) { alb = vec3f(0.10, 0.085, 0.06); }
if (m == 25) { alb = vec3f(0.42, 0.41, 0.38); }
// the piece lights the room — warm wash falling off from the screen
let toS = vec3f(0.0, 2.0, 4.8) - pos;
let dS = length(toS);
let facing = clamp(dot(n, toS / dS), 0.0, 1.0) + 0.18;
let glow = vec3f(1.0, 0.72, 0.45) * 2.6 / (1.0 + dS * dS * 0.10) * facing;
// a cool dim house light so the dark end has shape
let toE = vec3f(7.8, 2.0, 0.0) - pos;
let dE = length(toE);
let silkGlow = vec3f(0.30, 0.55, 0.62) * 1.3 / (1.0 + dE * dE * 0.10) * (clamp(dot(n, toE / dE), 0.0, 1.0) + 0.15);
let toW = vec3f(-7.8, 2.0, 0.0) - pos;
let dW = length(toW);
let planGlow = vec3f(0.34, 0.36, 0.44) * 0.9 / (1.0 + dW * dW * 0.10) * (clamp(dot(n, toW / dW), 0.0, 1.0) + 0.15);
let toR = vec3f(-4.0, 2.0, -4.8) - pos;
let dR = length(toR);
let rainGlow = vec3f(0.30, 0.30, 0.40) * 0.8 / (1.0 + dR * dR * 0.12) * (clamp(dot(n, toR / dR), 0.0, 1.0) + 0.12);
let toP = vec3f(4.0, 2.0, -4.8) - pos;
let dP = length(toP);
let poolGlow = vec3f(0.16, 0.42, 0.48) * 0.9 / (1.0 + dP * dP * 0.12) * (clamp(dot(n, toP / dP), 0.0, 1.0) + 0.12);
let toH = vec3f(0.0, 4.0, -3.0) - pos;
let dH = length(toH);
let house = vec3f(0.25, 0.30, 0.40) * 0.5 / (1.0 + dH * dH * 0.12) * (clamp(dot(n, toH / dH), 0.0, 1.0) + 0.1);
let toSn = vec3f(-7.8, 2.0, 2.85) - pos;
let dSn = length(toSn);
let snowGlow = vec3f(0.42, 0.46, 0.55) * 0.9 / (1.0 + dSn * dSn * 0.12) * (clamp(dot(n, toSn / dSn), 0.0, 1.0) + 0.12);
// the ball throws its spinning spots into the corner
let disco = mod_sm_disco(pos, n, time);
return alb * (glow + silkGlow + planGlow + rainGlow + poolGlow + house + snowGlow + disco + vec3f(0.030, 0.032, 0.040));
}
// ── SILK — the second piece (east wall): cool aurora, domain-warped fbm,
// slow filaments of teal and violet. The counterweight to the warm doors.
fn mod_rd_silk(uv: vec2f, time: f32) -> vec3f {
let p = uv * 1.8;
let t = time * 0.05;
let q = vec2f(fbm4(p + vec2f(t, -t * 0.7)), fbm4(p + vec2f(-t * 0.6, t)));
let r = vec2f(fbm4(p + q * 1.8 + vec2f(1.7, 9.2)), fbm4(p + q * 1.8 + vec2f(8.3, 2.8)));
let v = fbm5(p + r * 1.6);
var col = palette(v + t * 0.4,
vec3f(0.16, 0.20, 0.30), vec3f(0.22, 0.28, 0.32),
vec3f(1.0, 1.0, 1.0), vec3f(0.55, 0.72, 0.85));
col *= 0.35 + 0.9 * v;
// filaments: the bright threads in the weave
let fil = pow(clamp(1.0 - abs(fract(v * 3.0 + r.x) - 0.5) * 2.0, 0.0, 1.0), 8.0);
col += vec3f(0.45, 0.85, 0.95) * fil * (0.35 + 0.4 * sin(t * 3.0 + v * 9.0));
col *= 1.0 - 0.25 * pow(max(abs(uv.x), abs(uv.y)), 6.0); // vignette
return col * 0.9;
}
// ══ SNOWMEN & THE DISCO CORNER ══════════════════════════════════════════════
// Cel-shaded visitors born in the SNOWFIELD. Pure analytic spheres/cylinders
// (no marching), 3-band toon + quantized rim + ink outline. Each is unique:
// variant → hat (4 kinds) × glasses (3 kinds) × hashed hues. A mirrored ball
// hangs in the south-east corner where they gather to dance.
// Population layout (2 vec4 per snowman): [x,y,z,yaw] + [variant,phase,state,scale].
fn mod_sm_hash(v: f32, k: f32) -> f32 {
return fract(sin(v * 127.1 + k * 311.7) * 43758.5453);
}
fn mod_sm_hue(v: f32, k: f32) -> vec3f {
return hsv2rgb(vec3f(mod_sm_hash(v, k), 0.72, 0.95));
}
fn mod_sm_ball() -> vec3f { return vec3f(5.3, 3.3, -3.1); }
fn mod_sm_sph(ro: vec3f, rd: vec3f, c: vec3f, r: f32) -> f32 {
let oc = ro - c;
let b = dot(oc, rd);
let disc = b * b - dot(oc, oc) + r * r;
if (disc < 0.0) { return -1.0; }
let t = -b - sqrt(disc);
return select(-1.0, t, t > 0.0);
}
fn mod_sm_cyl(ro: vec3f, rd: vec3f, c: vec3f, h: f32, r: f32) -> f32 {
let o = ro - c;
var best = -1.0;
let a = rd.x * rd.x + rd.z * rd.z;
if (a > 0.00001) {
let b = o.x * rd.x + o.z * rd.z;
let disc = b * b - a * (o.x * o.x + o.z * o.z - r * r);
if (disc > 0.0) {
let t = (-b - sqrt(disc)) / a;
if (t > 0.0 && abs(o.y + rd.y * t) < h) { best = t; }
}
}
if (abs(rd.y) > 0.00001) {
for (var k = 0; k < 2; k++) {
let yc = select(-h, h, k == 0);
let t = (yc - o.y) / rd.y;
let px = o.x + rd.x * t;
let pz = o.z + rd.z * t;
if (t > 0.0 && px * px + pz * pz < r * r && (best < 0.0 || t < best)) { best = t; }
}
}
return best;
}
// trace every visitor + the ball; returns (toon rgb, t) with t = -1 on miss
fn mod_sm_trace(ro: vec3f, rd: vec3f, tMax: f32, time: f32) -> vec4f {
var bT = tMax;
var pid = -1;
var pc = vec3f(0.0);
var pcyl = false;
var pch = 0.0;
var pv = 0.0;
var pyaw = 0.0;
var pst = 0.0;
// the mirrored ball and its wire
let bp = mod_sm_ball();
{ let t = mod_sm_sph(ro, rd, bp, 0.28);
if (t > 0.0 && t < bT) { bT = t; pid = 20; pc = bp; pcyl = false; } }
{ let t = mod_sm_cyl(ro, rd, vec3f(bp.x, 3.88, bp.z), 0.30, 0.012);
if (t > 0.0 && t < bT) { bT = t; pid = 21; pc = vec3f(bp.x, 3.88, bp.z); pcyl = true; pch = 0.30; } }
// the snowmen
let n2 = popCount() / 2;
for (var j = 0; j < 6; j++) {
if (j >= n2) { break; }
let A = pop(j * 2);
let B = pop(j * 2 + 1);
if (B.z < 0.5) { continue; } // still inside the picture
let s = clamp(B.w, 0.03, 1.2);
let base = A.xyz;
let bc = base + vec3f(0.0, 0.75 * s, 0.0);
let tb = dot(bc - ro, rd);
if (tb < -1.5 * s || tb - 1.2 * s > bT) { continue; }
let q = bc - ro - rd * max(tb, 0.0);
if (dot(q, q) > 2.2 * s * s) { continue; }
let hat = i32(B.x + 0.5) % 4;
{ let c = base + vec3f(0.0, 0.30, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.30 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 0; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 0.72, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.22 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 1; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 0.88, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.175 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 2; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.02, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.16 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 3; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
if (hat == 0) { // top hat
{ let c = base + vec3f(0.0, 1.155, 0.0) * s; let t = mod_sm_cyl(ro, rd, c, 0.014 * s, 0.20 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 4; pc = c; pcyl = true; pch = 0.014 * s; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.27, 0.0) * s; let t = mod_sm_cyl(ro, rd, c, 0.10 * s, 0.115 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 5; pc = c; pcyl = true; pch = 0.10 * s; pv = B.x; pyaw = A.w; pst = B.z; } }
} else if (hat == 1) { // beanie + pompom
{ let c = base + vec3f(0.0, 1.11, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.15 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 6; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.285, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.058 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 7; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
} else if (hat == 2) { // little wizard cone
{ let c = base + vec3f(0.0, 1.16, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.105 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 8; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.255, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.07 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 8; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.33, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.045 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 8; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.39, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.035 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 7; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
} else { // wide brim
{ let c = base + vec3f(0.0, 1.135, 0.0) * s; let t = mod_sm_cyl(ro, rd, c, 0.010 * s, 0.24 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 4; pc = c; pcyl = true; pch = 0.010 * s; pv = B.x; pyaw = A.w; pst = B.z; } }
{ let c = base + vec3f(0.0, 1.19, 0.0) * s; let t = mod_sm_sph(ro, rd, c, 0.10 * s);
if (t > 0.0 && t < bT) { bT = t; pid = 6; pc = c; pcyl = false; pv = B.x; pyaw = A.w; pst = B.z; } }
}
}
if (pid < 0) { return vec4f(0.0, 0.0, 0.0, -1.0); }
// ── shade the winner
let hp = ro + rd * bT;
var n = vec3f(0.0, 1.0, 0.0);
if (pcyl) {
let ly = hp.y - pc.y;
if (abs(ly) > pch * 0.96) { n = vec3f(0.0, sign(ly), 0.0); }
else { n = normalize(vec3f(hp.x - pc.x, 0.0, hp.z - pc.z)); }
} else {
n = normalize(hp - pc);
}
if (pid == 20) {
// the mirrored ball: spinning facet glitter, HDR glints
let az = atan2(n.z, n.x) + time * 0.7;
let cell = vec2f(floor(az * 5.093), floor(n.y * 5.0));
let h = hash21(cell);
var mcol = vec3f(0.55, 0.60, 0.72) * (0.4 + 0.6 * clamp(n.y * 0.5 + 0.5, 0.0, 1.0));
mcol = mix(mcol, hsv2rgb(vec3f(fract(h + time * 0.05), 0.55, 1.0)), step(0.72, h) * 0.8);
mcol += vec3f(3.5) * step(0.93, fract(h * 7.13 + time * 0.23));
if (dot(n, -rd) < 0.30) { mcol = vec3f(0.04, 0.05, 0.09); }
return vec4f(mcol, bT);
}
if (pid == 21) { return vec4f(0.05, 0.05, 0.07, bT); }
// snowman base colors
var basecol = vec3f(0.93, 0.96, 1.0);
if (pid == 2) { basecol = mod_sm_hue(pv, 2.0) * 0.9; }
if (pid == 4 || pid == 5 || pid == 6 || pid == 8) { basecol = mod_sm_hue(pv, 1.0) * 0.85; }
if (pid == 7) { basecol = vec3f(0.98, 0.95, 0.90); }
if (pid == 5 && hp.y < pc.y - pch * 0.30) { basecol = mod_sm_hue(pv, 3.0) * 0.45; } // hat band
// ── DANCE DISCO SKIN (Galen, Sep 4): while a snowman DANCES (state 3) its
// snow itself cycles through party colors on the beat — each dancer offset by
// its variant so the ring shimmers, never in lockstep. Face decals paint
// after this, so carrots, smiles and shades stay readable on the color.
if (pst > 2.5 && pst < 3.5 && (pid == 0 || pid == 1 || pid == 3)) {
let danceHue = fract(time * 0.22 + pv * 0.37);
let pulse = 0.55 + 0.45 * sin(time * 5.9 + pv * 1.7);
basecol = mix(basecol, hsv2rgb(vec3f(danceHue, 0.85, 1.0)), 0.50 + 0.35 * pulse);
}
var ink = false;
if (pid == 3) {
// ── the face, as decals in the head's yaw frame
let fw = vec3f(sin(pyaw), 0.0, cos(pyaw));
let rt = vec3f(cos(pyaw), 0.0, -sin(pyaw));
let az = atan2(dot(n, rt), dot(n, fw));
let el = n.y;
if (abs(az) < 1.35) {
let g = (i32(pv + 0.5) / 4) % 3;
// blush
if (length(vec2f(abs(az) - 0.78, (el + 0.14) * 1.4)) < 0.16) { basecol = mix(basecol, vec3f(1.0, 0.55, 0.62), 0.55); }
// smile — a little ink arc
if (el < -0.02 && abs(length(vec2f(az, (el + 0.08) * 1.35)) - 0.20) < 0.035) { ink = true; }
// carrot
if (length(vec2f(az, (el - 0.03) * 1.3)) < 0.13) { basecol = vec3f(1.0, 0.52, 0.15); ink = false; }
let eL = length(vec2f(az - 0.40, (el - 0.30) * 1.4));
let eR = length(vec2f(az + 0.40, (el - 0.30) * 1.4));
if (g == 0) {
// round spectacles: ink rims, coal eyes inside
if (abs(eL - 0.17) < 0.05 || abs(eR - 0.17) < 0.05) { ink = true; }
if (abs(el - 0.30) < 0.035 && abs(az) < 0.25) { ink = true; } // bridge
if (eL < 0.07 || eR < 0.07) { ink = true; }
} else if (g == 1) {
// star shades: big dark lenses with a colored rim + white glint
if (eL < 0.22 || eR < 0.22) { basecol = mod_sm_hue(pv, 3.0) * 0.22; ink = false; }
if (abs(eL - 0.22) < 0.045 || abs(eR - 0.22) < 0.045) { basecol = mod_sm_hue(pv, 3.0); ink = false; }
if (length(vec2f(az - 0.33, (el - 0.36) * 1.4)) < 0.045 || length(vec2f(az + 0.47, (el - 0.36) * 1.4)) < 0.045) { basecol = vec3f(1.0); }
if (abs(el - 0.30) < 0.035 && abs(az) < 0.25) { basecol = mod_sm_hue(pv, 3.0); }
} else {
// one wide visor
if (abs(el - 0.30) < 0.15 && abs(az) < 0.62) {
basecol = mod_sm_hue(pv, 3.0) * 0.55; ink = false;
if (el > 0.38) { basecol = mix(basecol, vec3f(1.0), 0.7); } // top shine
}
}
}
}
// ── 3-band toon + warm kiss from the doors + quantized cool rim
let L = normalize(vec3f(0.18, 0.50, 0.85));
let dif = dot(n, L);
var band = 0.44;
if (dif > 0.10) { band = 0.72; }
if (dif > 0.56) { band = 1.0; }
var col = basecol * band;
col += vec3f(1.0, 0.72, 0.45) * clamp(n.z, 0.0, 1.0) * 0.10;
// party tint when near the ball
let bd = length(hp - bp);
if (bd < 4.0) {
let paz = atan2(hp.z - bp.z, hp.x - bp.x) * 3.0 - time * 2.1;
col += hsv2rgb(vec3f(fract(paz * 0.159 + time * 0.03), 0.7, 1.0)) * 0.22 * (1.0 - bd * 0.22) * (0.6 + 0.4 * sin(time * 7.33));
}
let rim = pow(1.0 - clamp(dot(n, -rd), 0.0, 1.0), 3.0);
col += vec3f(0.45, 0.60, 0.95) * step(0.42, rim) * 0.25;
let capHit = pcyl && abs(hp.y - pc.y) > pch * 0.96;
if (ink || dot(n, -rd) < select(0.30, 0.12, capHit)) { col = vec3f(0.04, 0.05, 0.09); }
return vec4f(col, bT);
}
// ── disco spots the ball throws on walls and floor (used inside mod_rg_shade)
fn mod_sm_disco(pos: vec3f, n: vec3f, time: f32) -> vec3f {
let bp = mod_sm_ball();
let to = pos - bp;
let dist = length(to);
if (dist > 5.5 || dist < 0.35) { return vec3f(0.0); }
let dir = to / dist;
let az = atan2(dir.z, dir.x) + time * 0.9;
let sc = vec2f(5.093, 4.0);
let cell = floor(vec2f(az, dir.y) * sc);
let h = hash21(cell);
if (h < 0.5) { return vec3f(0.0); }
let dd = length(vec2f(az, dir.y) * sc - cell - vec2f(0.5));
let spot = smoothstep(0.32, 0.12, dd);
let pulse = 0.65 + 0.35 * sin(time * 7.33 + h * 6.28);
let colr = hsv2rgb(vec3f(fract(h * 5.13 + time * 0.06), 0.75, 1.0));
return colr * spot * pulse * 3.0 / (1.0 + dist * dist * 0.08) * (clamp(dot(n, -dir), 0.0, 1.0) + 0.25);
}
// ── the babies: snowmen growing INSIDE the snowfield piece (2D toon)
fn mod_sm_panel(p0: vec2f, time: f32) -> vec4f {
var outc = vec4f(0.0);
let n2 = popCount() / 2;
for (var j = 0; j < 6; j++) {
if (j >= n2) { break; }
let B = pop(j * 2 + 1);
if (B.z > 0.5) { continue; } // only the growing ones
let v = B.x;
let ph = clamp(B.y, 0.0, 1.0);
let dpt = mod_sm_hash(v, 21.0);
let gx = (mod_sm_hash(v, 20.0) - 0.5) * 1.2;
let gy = -0.62 + dpt * 0.30;
let sz = (0.30 - dpt * 0.13) * (0.25 + 0.75 * ph);
let q = (p0 - vec2f(gx, gy)) / sz;
if (abs(q.x) > 1.6 || q.y < -0.2 || q.y > 2.6) { continue; }
let d1 = length(q - vec2f(0.0, 0.42)) - 0.42;
let d2 = length(q - vec2f(0.0, 1.05)) - 0.30;
let d3 = length(q - vec2f(0.0, 1.47)) - 0.215;
var d = min(d1, min(d2, d3));
let hat = i32(v + 0.5) % 4;
var hd = 99.0;
if (hat == 0) { hd = min(sdBox(q - vec2f(0.0, 1.70), vec2f(0.30, 0.035)), sdBox(q - vec2f(0.0, 1.87), vec2f(0.16, 0.15))); }
else if (hat == 1) { hd = min(length(q - vec2f(0.0, 1.68)) - 0.19, length(q - vec2f(0.0, 1.90)) - 0.08); }
else if (hat == 2) { hd = min(min(length(q - vec2f(0.0, 1.70)) - 0.14, length(q - vec2f(0.0, 1.83)) - 0.09), length(q - vec2f(0.0, 1.93)) - 0.055); }
else { hd = min(sdBox(q - vec2f(0.0, 1.665), vec2f(0.34, 0.03)), length(q - vec2f(0.0, 1.74)) - 0.13); }
let dAll = min(d, hd);
if (dAll > 0.10) { continue; }
var c = vec3f(0.95, 0.97, 1.0);
if (hd < d) { c = mod_sm_hue(v, 1.0) * 0.85; }
// face
if (length(q - vec2f(-0.085, 1.53)) < 0.045 || length(q - vec2f(0.085, 1.53)) < 0.045) { c = vec3f(0.05, 0.06, 0.09); }
if (length(q - vec2f(0.0, 1.44)) < 0.05) { c = vec3f(1.0, 0.52, 0.15); }
// toon shadow side + ink outline
c *= mix(0.72, 1.0, smoothstep(-0.2, 0.35, q.x));
if (dAll > -0.045) { c = vec3f(0.05, 0.06, 0.09); }
let alpha = smoothstep(0.0, 0.22, ph) * smoothstep(0.10, 0.0, dAll);
// a shimmer of build-snow while young
let shim = smoothstep(0.6, 0.0, ph) * step(0.93, hash21(floor(q * 9.0) + vec2f(floor(time * 3.0), v)));
if (alpha > outc.a) { outc = vec4f(mix(c, vec3f(1.0), shim), alpha); }
}
return outc;
}
// ── SNOWFIELD — the west wall's second piece: a wind-scoured snowfield at
// blue dusk. One light of truth: the low sun smoking through a cloud break;
// drift crests catch its amber rim, the hollows fall to blue; spindrift
// streams off the tops and loose flakes run with the gusts.
fn mod_rd_snow(uv: vec2f, time: f32) -> vec3f {
let p = vec2f(uv.x, -uv.y); // y up
let t = time * 0.55;
let gust = 0.65 + 0.35 * vnoise(vec2f(t * 0.5, 3.7)); // the wind breathes
let sunP = vec2f(0.38, 0.16);
// sky: pale violet-grey, one warm break
var col = mix(vec3f(0.30, 0.33, 0.42), vec3f(0.55, 0.52, 0.55),
clamp(1.0 - p.y * 1.6, 0.0, 1.0) * 0.5);
let sd = length(p - sunP);
col += vec3f(1.0, 0.72, 0.45) * 0.85 * exp(-sd * sd * 9.0);
col += vec3f(1.3, 1.05, 0.8) * exp(-sd * sd * 90.0);
// cloud shreds crossing the break
let cl = fbm4(vec2f(p.x * 2.2 - t * 0.14, p.y * 5.0));
col = mix(col, vec3f(0.34, 0.36, 0.44),
smoothstep(0.45, 0.75, cl) * 0.55 * smoothstep(0.02, 0.25, p.y));
// the ridge asleep on the horizon
let ridge = 0.10 + 0.10 * fbm4(vec2f(p.x * 1.7 + 4.2, 2.0)) - 0.06 * p.x * p.x;
let rmask = smoothstep(ridge + 0.012, ridge - 0.012, p.y);
col = mix(col, vec3f(0.16, 0.18, 0.24), rmask * smoothstep(-0.05, 0.08, p.y));
// the snowfield: sastrugi carved along the wind
if (p.y < 0.05) {
let depth = clamp((0.05 - p.y) * 1.4, 0.0, 1.0);
let scale = mix(14.0, 3.2, depth);
let sp = vec2f(p.x * scale * 0.55, (p.y + 1.0) * scale);
let h = fbm4(vec2f(sp.x * 0.9 - t * 0.05, sp.y * 3.1));
let h2 = fbm3(vec2f(sp.x * 2.6 - t * 0.10, sp.y * 7.0));
let slope = h - fbm4(vec2f(sp.x * 0.9 - t * 0.05 - 0.22, sp.y * 3.1));
var snow = mix(vec3f(0.52, 0.58, 0.70), vec3f(0.88, 0.90, 0.95),
clamp(0.5 + slope * 3.0, 0.0, 1.0));
snow += vec3f(1.0, 0.72, 0.45) * clamp(slope * 5.0, 0.0, 1.0) * 0.35 *
exp(-abs(p.x - sunP.x) * 1.2); // amber rims toward the sun
snow -= vec3f(0.10, 0.07, 0.0) * clamp(h2 - 0.5, 0.0, 1.0) * 0.5; // hollows go blue
// frost sparkle — high-frequency, twinkling, only on lit slopes
let spark = step(0.985, hash21(floor(sp * 24.0) + vec2f(floor(t * 1.5), 0.0)));
snow += vec3f(1.0, 0.95, 0.85) * spark * clamp(slope * 4.0, 0.0, 1.0) * 0.5;
col = mix(col, snow, smoothstep(0.05, 0.02, p.y));
}
// spindrift: snow smoking off the drifts, advected + domain-warped
var drift = 0.0;
for (var i = 0; i < 3; i++) {
let fi = f32(i);
let sc = 1.6 + fi * 1.3;
let sp2 = vec2f(p.x * sc - t * (1.4 + fi * 0.9) * gust, p.y * sc * 6.0 + fi * 7.3);
let w = vnoise(sp2 + vec2f(fbm3(sp2 * 0.7 + vec2f(t * 0.3, 0.0)), 0.0) * 1.4);
let band = exp(-max(p.y + 0.1 + fi * 0.12, 0.0) * 5.0) + 0.12;
drift += smoothstep(0.58, 0.9, w) * band * (0.5 - fi * 0.12);
}
col = mix(col, vec3f(0.85, 0.88, 0.94), clamp(drift * gust, 0.0, 0.8));
// loose flakes streaking with the wind
for (var k = 0; k < 2; k++) {
let fk = f32(k);
let fp = vec2f(p.x * (3.0 + fk * 2.0) - t * (2.2 + fk) * gust,
p.y * (10.0 + fk * 6.0) - t * (0.25 + fk * 0.1));
let cell = floor(fp);
let f = fract(fp) - vec2f(0.5);
let jit = hash22(cell) - vec2f(0.5);
let dd = length(f - jit * 0.6);
col += vec3f(0.9, 0.92, 0.97) * smoothstep(0.10 - fk * 0.03, 0.02, dd) *
(0.35 - fk * 0.1) * step(0.55, hash21(cell + vec2f(9.1)));
}
// one terminal grade keyed to luminance: cold lifts the darks, the sun keeps its warmth
let lum = dot(col, vec3f(0.299, 0.587, 0.114));
col = mix(vec3f(0.20, 0.24, 0.34) + col * 0.6, col, clamp(lum * 1.6, 0.0, 1.0));
// the babies grow here, in the picture, before they fly out
let baby = mod_sm_panel(p, time);
col = mix(col, baby.rgb, baby.a);
col *= 1.0 - 0.25 * pow(max(abs(uv.x), abs(uv.y)), 6.0);
return col;
}
fn mod_rg_screenUv2(pos: vec3f) -> vec2f {
return vec2f(-pos.z / 1.30, -(pos.y - 2.0) / 1.30);
}
fn mod_rg_onScreen2(pos: vec3f, n: vec3f) -> bool {
let uv = mod_rg_screenUv2(pos);
return pos.x > 7.7 && n.x < -0.5 && abs(uv.x) < 1.0 && abs(uv.y) < 1.0;
}
// ── THE PLAN OF LIGHT (west) — the doors' light seen from directly above,
// a live architectural drawing: same uniforms, same fans, second point of view.
fn mod_rd_plan(uv: vec2f, time: f32) -> vec3f {
let x = uv.x * 9.0;
let z = 8.9 - (uv.y + 1.0) * 6.5; // top of sheet = outside the wall
var col = vec3f(0.014, 0.016, 0.024); // night paper
// survey grid, one-metre
let gr = abs(fract(vec2f(x, z)) - 0.5);
col += vec3f(0.05, 0.06, 0.09) * (smoothstep(0.48, 0.5, max(gr.x, gr.y))) * 0.5;
// THE LIGHT — the very gate that lights the lobby, drawn on the floor plan
let g = mod_rd_gate(vec3f(x, 0.05, z));
col += vec3f(1.0, 0.72, 0.40) * g * 1.1;
// the wall, gapped at the doors
var wallInk = smoothstep(0.30, 0.22, abs(z - 8.2));
for (var i = 0; i < 3; i++) { wallInk *= 1.0 - smoothstep(1.16, 1.06, abs(x - mod_rd_doorx(i))); }
col += vec3f(0.55, 0.58, 0.66) * wallInk * 0.8;
for (var i = 0; i < 3; i++) {
let c = vec2f(mod_rd_doorx(i), 8.0);
let lp = vec2f(x, z) - c;
let r = length(lp);
// drum ring
col += vec3f(0.60, 0.64, 0.72) * smoothstep(0.05, 0.02, abs(r - 1.13)) * 0.9;
// the four wings, turning live
let a = uni(40 + i);
let wing = pow(abs(cos(2.0 * (atan2(lp.y, lp.x) + a))), 60.0);
col += vec3f(0.85, 0.88, 0.95) * wing * smoothstep(1.05, 0.9, r) * step(0.06, r) * 0.9;
}
// the people — live dots crossing the sheet
for (var j = 0; j < 3; j++) {
if (uni(73 + j * 4) > 0.5) {
let pd = length(vec2f(x, z) - vec2f(uni(70 + j * 4), uni(71 + j * 4)));
col += vec3f(1.0, 0.95, 0.85) * smoothstep(0.20, 0.06, pd);
}
}
col *= 1.0 - 0.3 * pow(max(abs(uv.x), abs(uv.y)), 6.0);
return col;
}
// ── NIGHT RAIN (south-left) — a city through running rain on glass.
fn mod_rd_bokeh(q: vec2f) -> vec3f {
// a city out of focus: warm haze low, big soft discs of lamplight
var col = mix(vec3f(0.055, 0.045, 0.070), vec3f(0.020, 0.022, 0.040), q.y * 0.5 + 0.5);
col += vec3f(0.28, 0.16, 0.08) * exp(-abs(q.y + 0.55) * 2.2); // street glow
for (var i = 0; i < 12; i++) {
let h = hash22(vec2f(f32(i) * 7.3 + 1.7, f32(i) * 3.9 + 9.2));
let hc = hash21(vec2f(f32(i) * 5.1, 3.3));
let c = vec2f((h.x * 2.0 - 1.0) * 1.15, (h.y * 2.0 - 1.0) * 0.7 - 0.15);
let r = 0.14 + hc * 0.26;
let d = length(q - c);
var tint = vec3f(1.0, 0.72, 0.38); // sodium
if (hc > 0.6) { tint = vec3f(0.50, 0.72, 1.0); } // mercury
if (hc > 0.85) { tint = vec3f(1.0, 0.38, 0.35); } // tail-light
col += tint * smoothstep(r, r * 0.15, d) * (0.06 + hc * 0.14);
col += tint * exp(-d * d / (r * r) * 2.0) * 0.05; // halo
}
return col;
}
fn mod_rd_dropLayer(q: vec2f, t: f32) -> vec3f { // (offset.xy, mask)
let cell = vec2f(6.0, 1.2);
let id = floor(q * cell);
let rnd = hash22(id);
let f = fract(q * cell) - 0.5;
let yfall = fract(t * (0.18 + rnd.y * 0.22) + rnd.x * 7.0);
let dpos = vec2f((rnd.x - 0.5) * 0.6, (0.5 - yfall) * 0.9 + sin(t * 3.0 + rnd.y * 9.0) * 0.05);
let d = length((f - dpos) * vec2f(1.0, 0.5));
let mask = smoothstep(0.14, 0.05, d) * step(rnd.y, 0.55); // most cells hold no drop
return vec3f((f - dpos) * mask, mask);
}
fn mod_rd_rain(uv: vec2f, time: f32) -> vec3f {
let t = time;
let l1 = mod_rd_dropLayer(uv * 1.4, t);
let l2 = mod_rd_dropLayer(uv * 2.6 + vec2f(3.7, 1.1), t * 1.3);
let off = l1.xy * 0.55 + l2.xy * 0.30;
var col = mod_rd_bokeh(uv + off);
col = mix(col, mod_rd_bokeh(uv * 0.98), 0.35); // pane haze
let mask = max(l1.z, l2.z);
col += vec3f(1.0, 0.95, 0.85) * pow(mask, 8.0) * 0.22; // droplet glints
// streak trails, faint
let lane = hash21(vec2f(floor(uv.x * 20.0), 2.0));
let tr = fract(uv.y * 2.0 + t * 0.04 + lane * 5.0);
col += vec3f(0.07, 0.08, 0.11) * smoothstep(0.12, 0.0, abs(fract(uv.x * 20.0) - 0.5) * 2.0 - 0.2) * smoothstep(0.75, 1.0, tr) * step(lane, 0.4) * 0.25;
col *= 1.0 - 0.3 * pow(max(abs(uv.x), abs(uv.y)), 6.0);
return col;
}
// ── POOLLIGHT (south-right) — sun through water: caustics on tiled blue.
fn mod_rd_pool(uv: vec2f, time: f32) -> vec3f {
let t = time * 0.35;
let p = uv * 2.6;
let sway = vec2f(fbm3(p * 0.7 + vec2f(t * 0.4, 0.0)), fbm3(p * 0.7 + vec2f(0.0, t * 0.3))) * 0.35;
let v1 = voronoi(p + sway + vec2f(t, t * 0.6));
let v2 = voronoi(p * 1.7 - sway + vec2f(-t * 0.8, t));
let c1 = pow(clamp(1.0 - v1.x, 0.0, 1.0), 5.0);
let c2 = pow(clamp(1.0 - v2.x, 0.0, 1.0), 5.0);
let caust = pow(c1 * 0.7 + c2 * 0.55, 1.6) * 2.6;
// tiled floor, refracted by the same sway
let gp = p * 2.0 + sway * 1.8;
let gl = abs(fract(gp) - 0.5);
let grout = smoothstep(0.44, 0.5, max(gl.x, gl.y));
var base = mix(vec3f(0.05, 0.22, 0.30), vec3f(0.03, 0.14, 0.22), uv.y * 0.5 + 0.5);
base *= 1.0 - grout * 0.45;
var col = base * (0.55 + caust) + vec3f(0.75, 0.95, 1.0) * caust * 0.30;
col += vec3f(0.30, 0.70, 0.80) * smoothstep(0.4, -1.0, uv.y) * 0.15; // depth glow
col *= 1.0 - 0.3 * pow(max(abs(uv.x), abs(uv.y)), 6.0);
return col;
}
// ── THE TELEVISION (east) — an uploaded clip PLAYING: the still is driven like
// live VHS/CRT footage (scrolling scanlines, tracking jitter, a hold bar rolling
// up, chroma bleed, per-frame grain, flicker) + a slow Ken Burns drift so it
// reads as a video playing out. Interactive: uni(51)=power(eased 0..1),
// uni(52)=hover. Sprite slot 0 = "muppets".
// ── CUTOUT ANIMATION — the figures move as PARTS, the wall does not. Each
// puppet is segmented from the beige wall (color key + its own bbox), treated
// as a rigid cutout pivoting at its base behind the counter, and re-composited
// over a cleaned background plate. Conceptual model (also written to the swarm
// brain): figure = pixels + place + pivot + role; background = held still.
fn mod_rd_wallish(c: vec3f) -> bool {
// the beige wall: warm but BALANCED (r≈g>b) and bright. The balance test
// (g close to r) is what separates wall from the orange/pink fur, which is
// r-dominant; the brightness floor separates it from the dark puppet.
let lum = dot(c, vec3f(0.299, 0.587, 0.114));
return lum > 0.30 && c.r > c.b * 1.2 && c.g > c.r * 0.62 && c.g > c.b * 1.02;
}
// inverse rigid transform: where did this output pixel come FROM in the still?
fn mod_rd_srcOf(cell: vec2f, pivot: vec2f, phase: f32, t: f32) -> vec2f {
let ang = 0.055 * sin(phase) + 0.02 * sin(t * 1.7 + phase * 0.5);
let head = smoothstep(0.80, 0.35, cell.y); // 1 at the head, 0 at the base
let disp = vec2f(0.014 * sin(phase * 1.7), 0.007 * sin(phase * 2.3) - 0.010 * sin(phase * 1.1)) * head;
let rel = cell - pivot - disp;
let ca = cos(-ang); let sa = sin(-ang);
return pivot + vec2f(rel.x * ca - rel.y * sa, rel.x * sa + rel.y * ca);
}
fn mod_rd_whiteish(c: vec3f) -> bool {
let sat = max(max(c.r, c.g), c.b) - min(min(c.r, c.g), c.b);
return dot(c, vec3f(0.299, 0.587, 0.114)) > 0.45 && sat < 0.16; // counter / the "0" / white bg
}
// one puppet cutout: bbox-gated; only the FIGURE body (not wall, not white
// counter/numeral) is captured. Returns (rgb, coverage).
fn mod_rd_cut(cell: vec2f, x0: f32, x1: f32, top: f32, phase: f32, t: f32) -> vec4f {
let pivot = vec2f((x0 + x1) * 0.5, 0.82);
let src = mod_rd_srcOf(cell, pivot, phase, t);
if (src.x < x0 || src.x > x1 || src.y < top || src.y > 0.86) { return vec4f(0.0); }
let c = sprite(0, clamp(src, vec2f(0.001), vec2f(0.999))).rgb;
if (mod_rd_wallish(c) || mod_rd_whiteish(c)) { return vec4f(0.0); }
return vec4f(c, 1.0);
}
// recolour a figure into a new hue while preserving its own shading (luma),
// so it works on the near-black centre puppet as well as the coloured ones.
fn mod_rd_recolor(c: vec3f, tint: vec3f, amt: f32, boost: f32) -> vec3f {
let lum = dot(c, vec3f(0.299, 0.587, 0.114));
let shaded = tint * lum * boost;
return clamp(mix(c, shaded, amt), vec3f(0.0), vec3f(1.5));
}
// luma-preserving hue rotation (row form) — recolours a figure while the white
// numeral, being colourless, stays white. sat/val scale the result.
fn mod_rd_restyle(c: vec3f, a: f32, sat: f32, val: f32) -> vec3f {
let U = cos(a); let W = sin(a);
let r = dot(c, vec3f(0.299 + 0.701 * U + 0.168 * W, 0.587 - 0.587 * U + 0.330 * W, 0.114 - 0.114 * U - 0.497 * W));
let g = dot(c, vec3f(0.299 - 0.299 * U - 0.328 * W, 0.587 + 0.413 * U + 0.035 * W, 0.114 - 0.114 * U + 0.292 * W));
let b = dot(c, vec3f(0.299 - 0.300 * U + 1.250 * W, 0.587 - 0.588 * U - 1.050 * W, 0.114 + 0.886 * U - 0.203 * W));
let hue = vec3f(r, g, b);
let lum = dot(c, vec3f(0.299, 0.587, 0.114));
return clamp(mix(vec3f(lum), hue, sat) * val, vec3f(0.0), vec3f(1.6));
}
// ── THE WARP RIG — control handles anchored on each figure's anatomy deform
// the figure's OWN pixels. Two primitives, summed into a displacement field:
// pull = translate a feature (snout out, jaw open, head bob)
// bulge = scale a region about its centre (big head, beady eyes, +/- amount)
// Inverse-sampling src = cell - warp reshapes the real pixels; static handle
// offsets change the FORM (different creature), time-varying ones = BEHAVIOR.
fn mod_rd_pull(cell: vec2f, rest: vec2f, disp: vec2f, rad: f32) -> vec2f {
let d = (cell - rest) / rad;
return disp * exp(-dot(d, d));
}
fn mod_rd_bulge(cell: vec2f, ctr: vec2f, amt: f32, rad: f32) -> vec2f {
let d = (cell - ctr) / rad;
return (cell - ctr) * amt * exp(-dot(d, d));
}
// the rig for one figure (source-space cell in) — form handles + behavior handles
fn mod_rd_rig(cell: vec2f, fig: i32, t: f32) -> vec2f {
var w = vec2f(0.0);
if (fig == 0) { // LEFT (orange) → long-snouted, beady-eyed beast
w += mod_rd_bulge(cell, vec2f(0.28, 0.45), 0.16, 0.16); // bigger head
w += mod_rd_bulge(cell, vec2f(0.27, 0.40), -0.30, 0.055); // beady eyes (pinch)
w += mod_rd_pull(cell, vec2f(0.32, 0.53), vec2f(0.055, 0.02), 0.09); // snout juts out
w += mod_rd_pull(cell, vec2f(0.28, 0.44), vec2f(0.016 * sin(t * 2.0), 0.0), 0.24); // whole-head sway (behavior)
w += mod_rd_pull(cell, vec2f(0.33, 0.56), vec2f(0.0, 0.022 * max(0.0, sin(t * 3.2))), 0.06); // snout chew (behavior)
} else if (fig == 1) { // CENTRE (dark) → huge round head, big eyes
w += mod_rd_bulge(cell, vec2f(0.58, 0.47), 0.30, 0.15); // giant head
w += mod_rd_bulge(cell, vec2f(0.56, 0.49), 0.18, 0.07); // big eyes (swell)
w += mod_rd_pull(cell, vec2f(0.58, 0.45), vec2f(0.0, 0.016 * sin(t * 1.7)), 0.22); // breathe/bob (behavior)
w += mod_rd_pull(cell, vec2f(0.58, 0.56), vec2f(0.0, 0.024 * max(0.0, sin(t * 4.0 + 1.0))), 0.06); // mouth flap (behavior)
} else { // RIGHT (pink/teal) → beaky, crested bird
w += mod_rd_pull(cell, vec2f(0.80, 0.50), vec2f(-0.055, 0.008), 0.08); // beak juts toward the others
w += mod_rd_bulge(cell, vec2f(0.82, 0.40), 0.14, 0.09); // crested head
w += mod_rd_bulge(cell, vec2f(0.82, 0.40), -0.12, 0.045); // small sharp eye
w += mod_rd_pull(cell, vec2f(0.82, 0.41), vec2f(0.0, 0.018 * sin(t * 2.6)), 0.22); // head bob (behavior)
w += mod_rd_pull(cell, vec2f(0.80, 0.50), vec2f(-0.02 * max(0.0, sin(t * 5.0)), 0.0), 0.05); // beak talks (behavior)
}
return w;
}
// one figure: whole-entity rock (srcOf) + rig warp on its OWN pixels
fn mod_rd_rigcut(cell: vec2f, x0: f32, x1: f32, top: f32, fig: i32, phase: f32, t: f32) -> vec4f {
let base = mod_rd_srcOf(cell, vec2f((x0 + x1) * 0.5, 0.82), phase, t); // gross body motion
let src = base - mod_rd_rig(base, fig, t); // rig reshape
if (src.x < x0 - 0.02 || src.x > x1 + 0.02 || src.y < top || src.y > 0.86) { return vec4f(0.0); }
let c = sprite(0, clamp(src, vec2f(0.001), vec2f(0.999))).rgb;
if (mod_rd_wallish(c) || mod_rd_whiteish(c)) { return vec4f(0.0); }
return vec4f(c, 1.0);
}
// ── THE CAST — three ORIGINAL creatures, each a whole rigged entity with its
// own silhouette, palette, and behaviors. Drawn in aspect-corrected space g
// (circles stay round): g = ((cell.x-0.5)*1.337, cell.y-0.5), y down.
fn mod_rd_blink(t: f32, period: f32, off: f32) -> f32 {
let ph = fract((t + off) / period);
return smoothstep(0.0, 0.03, ph) * (1.0 - smoothstep(0.05, 0.09, ph)); // ~1 briefly = closed
}
// an eye: white sclera, tracking pupil, glint, upper eyelid drops on blink
fn mod_rd_eye(g: vec2f, ctr: vec2f, r: f32, look: vec2f, blink: f32, skin: vec3f) -> vec4f {
let d = sdCircle(g - ctr, r);
let cov = smoothstep(0.005, -0.004, d);
if (cov < 0.01) { return vec4f(0.0); }
var c = vec3f(0.96, 0.96, 0.99);
let pd = sdCircle(g - (ctr + look * r * 0.5), r * 0.44);
c = mix(c, vec3f(0.03, 0.03, 0.06), smoothstep(0.004, -0.003, pd));
c += vec3f(1.0) * smoothstep(0.006, 0.0, sdCircle(g - (ctr + look * r * 0.5 - vec2f(r * 0.2)), r * 0.13));
// eyelid: y above (ctr.y - r + blink*2r) is covered by skin
let lidY = ctr.y - r + blink * 2.0 * r;
c = mix(c, skin * 0.85, step(g.y, lidY));
return vec4f(c, cov);
}
fn mod_rd_shadeBody(base: vec3f, g: vec2f, cY: f32, d: f32) -> vec3f {
var c = base * (0.78 + 0.42 * smoothstep(0.14, -0.14, g.y - cY)); // top-lit
c = mix(c * 0.38, c, smoothstep(0.0, -0.022, d)); // dark rim/outline
return c;
}
// LEFT — "Sprout": green bulb, one big eye, two swaying antennae. Behavior: sway, blink, wiggle.
fn mod_rd_critL(g: vec2f, t: f32) -> vec4f {
let o = vec2f(-0.31 + 0.02 * sin(t * 1.6), 0.01 * sin(t * 2.2));
let gg = g - o;
var d = opSmoothUnion(sdCircle(gg - vec2f(0.0, 0.12), 0.13), sdCircle(gg - vec2f(0.0, -0.05), 0.10), 0.06);
let aw = 0.03 * sin(t * 3.0);
d = min(d, sdSegment(gg, vec2f(-0.03, -0.13), vec2f(-0.06 + aw, -0.25)) - 0.011);
d = min(d, sdSegment(gg, vec2f( 0.03, -0.13), vec2f( 0.06 - aw, -0.25)) - 0.011);
let cov = smoothstep(0.008, -0.006, d);
if (cov < 0.01) { return vec4f(0.0); }
var c = mod_rd_shadeBody(vec3f(0.27, 0.72, 0.34), g, o.y, d);
c += vec3f(0.55, 0.9, 0.5) * smoothstep(0.045, 0.0, sdCircle(gg - vec2f(-0.03, 0.12), 0.06)) * 0.5; // belly
let e = mod_rd_eye(g, o + vec2f(0.0, -0.055), 0.055, vec2f(0.4 * sin(t * 0.7), 0.3 * sin(t * 0.9)), mod_rd_blink(t, 3.3, 0.0), vec3f(0.27, 0.72, 0.34));
c = mix(c, e.rgb, e.a);
return vec4f(c, cov);
}
// CENTRE — "Bulb": round orange body, two ears, two eyes, opening mouth. Behavior: breathe, blink, talk.
fn mod_rd_critC(g: vec2f, t: f32) -> vec4f {
let o = vec2f(0.06, 0.0);
let gg = g - o;
let breath = 0.012 * sin(t * 1.7);
var d = opSmoothUnion(sdCircle(gg - vec2f(0.0, 0.10 + breath), 0.16), sdCircle(gg - vec2f(0.0, -0.05), 0.135), 0.07);
d = opSmoothUnion(d, sdCircle(gg - vec2f(-0.10, -0.16), 0.045), 0.03);
d = opSmoothUnion(d, sdCircle(gg - vec2f( 0.10, -0.16), 0.045), 0.03);
let cov = smoothstep(0.008, -0.006, d);
if (cov < 0.01) { return vec4f(0.0); }
var c = mod_rd_shadeBody(vec3f(0.95, 0.55, 0.22), g, o.y, d);
c += vec3f(1.0, 0.8, 0.5) * smoothstep(0.07, 0.0, sdCircle(gg - vec2f(0.0, 0.10), 0.09)) * 0.35;
let bl = mod_rd_blink(t, 2.7, 0.5);
let look = vec2f(0.35 * sin(t * 0.8 + 1.0), 0.25 * sin(t * 0.6));
var e = mod_rd_eye(g, o + vec2f(-0.055, -0.05), 0.05, look, bl, vec3f(0.95, 0.55, 0.22)); c = mix(c, e.rgb, e.a);
e = mod_rd_eye(g, o + vec2f( 0.055, -0.05), 0.05, look, bl, vec3f(0.95, 0.55, 0.22)); c = mix(c, e.rgb, e.a);
let mo = 0.5 + 0.5 * sin(t * 4.0);
let md = sdCircle((gg - vec2f(0.0, 0.06)) / vec2f(1.0, 0.45 + 0.6 * mo), 0.028);
c = mix(c, vec3f(0.25, 0.05, 0.06), smoothstep(0.006, -0.003, md));
return vec4f(c, cov);
}
// RIGHT — "Beak": tall blue bird, crest spikes, triangular opening beak, sharp eye. Behavior: bob, squawk, crest flick.
fn mod_rd_critR(g: vec2f, t: f32) -> vec4f {
let o = vec2f(0.40, -0.01 + 0.015 * sin(t * 2.6));
let gg = g - o;
var d = opSmoothUnion(sdCircle(gg - vec2f(0.0, 0.12), 0.115), sdCircle(gg - vec2f(-0.01, -0.06), 0.085), 0.05);
for (var i = 0; i < 3; i = i + 1) {
let fx = f32(i) - 1.0;
d = min(d, sdSegment(gg, vec2f(-0.01 + fx * 0.028, -0.13), vec2f(-0.01 + fx * 0.05, -0.22 - 0.012 * sin(t * 4.0 + fx))) - 0.010);
}
// beak — two thin triangles hinging open toward the others (−x)
let bo = 0.10 + 0.10 * max(0.0, sin(t * 5.0));
let bp = gg - vec2f(-0.075, -0.05);
let up = sdEquilateralTriangle(rotate(bp - vec2f(0.0, -0.01), -1.571 - bo) * 12.0, 1.0) / 12.0;
let lo = sdEquilateralTriangle(rotate(bp - vec2f(0.0, 0.01), 1.571 + bo) * 12.0, 1.0) / 12.0;
let beak = min(up, lo);
let cov = smoothstep(0.008, -0.006, min(d, beak));
if (cov < 0.01) { return vec4f(0.0); }
var c: vec3f;
if (beak < d) { c = mix(vec3f(0.95, 0.7, 0.2) * 0.4, vec3f(0.98, 0.72, 0.2), smoothstep(0.0, -0.02, beak)); }
else { c = mod_rd_shadeBody(vec3f(0.28, 0.5, 0.9), g, o.y, d); c += vec3f(0.5, 0.7, 1.0) * smoothstep(0.05, 0.0, sdCircle(gg - vec2f(0.0, 0.12), 0.06)) * 0.4; }
let e = mod_rd_eye(g, o + vec2f(0.01, -0.07), 0.04, vec2f(-0.4, 0.2 * sin(t * 1.1)), mod_rd_blink(t, 3.0, 1.3), vec3f(0.28, 0.5, 0.9));
c = mix(c, e.rgb, e.a);
return vec4f(c, cov);
}
// the full composited frame: warm studio wall · three creatures · counter · the "0"
fn mod_rd_tvframe(cell: vec2f, t: f32) -> vec3f {
let g = vec2f((cell.x - 0.5) * 1.337, cell.y - 0.5);
// set: warm beige wall with a soft vignette, a floor shadow line
var col = mix(vec3f(0.80, 0.70, 0.52), vec3f(0.62, 0.52, 0.40), smoothstep(-0.5, 0.5, g.y));
col *= 1.0 - 0.25 * dot(g, g);
// creatures — back (right) to front (left)
var f = mod_rd_critR(g, t); col = mix(col, f.rgb, f.a);
f = mod_rd_critC(g, t); col = mix(col, f.rgb, f.a);
f = mod_rd_critL(g, t); col = mix(col, f.rgb, f.a);
// the white numeral "0" the centre creature presents (a ring on its belly)
let zc = vec2f(0.06, 0.14);
let zr = abs(sdCircle((g - zc) / vec2f(0.72, 1.0), 0.085)) - 0.028;
col = mix(col, vec3f(0.98, 0.98, 1.0), smoothstep(0.006, -0.004, zr));
// the counter — a grey bar in FRONT of the creatures' lower bodies
let cd = sdRoundedBox(g - vec2f(0.0, 0.40), vec2f(0.9, 0.12), 0.03);
let cc = mix(vec3f(0.62, 0.63, 0.66), vec3f(0.42, 0.43, 0.47), smoothstep(-0.12, 0.12, g.y - 0.40));
col = mix(col, cc, smoothstep(0.006, -0.006, cd));
return col;
}
fn mod_rd_tv(uv: vec2f, time: f32) -> vec3f {
let on = uni(51);
let hov = uni(52);
// CRT barrel curvature
let r2 = dot(uv, uv);
var suv = uv * (1.0 + 0.05 * r2);
let edge = max(abs(suv.x), abs(suv.y));
let inside = step(edge, 1.0);
// Ken Burns — slow zoom + pan gives the still genuine motion
let kb = 1.0 - 0.05 * (0.5 + 0.5 * sin(time * 0.13));
var s2 = suv * kb + vec2f(sin(time * 0.10) * 0.02, cos(time * 0.08) * 0.014);
// VHS tracking: per-scanline horizontal jitter, retimed each frame
let jit = (hash21(vec2f(floor(s2.y * 120.0), floor(time * 24.0))) - 0.5) * 0.018
+ sin(s2.y * 44.0 + time * 3.0) * 0.003;
s2.x += jit;
// aspect-fit the landscape clip into the ~4:3 screen (thin letterbox)
let sz = spriteSize(0);
let imgA = sz.x / max(sz.y, 1.0);
let yscale = imgA / 1.337;
var cell = vec2f(s2.x, s2.y * yscale) * 0.5 + 0.5;
// crop the source's screenshot chrome (dark band + timestamp at the top,
// bezel slivers at the sides) out of the sampled window
cell = vec2f(0.03 + cell.x * 0.94, 0.10 + cell.y * 0.88);
let inImg = step(0.03, cell.x) * step(cell.x, 0.97) * step(0.10, cell.y) * step(cell.y, 0.98);
let cc = clamp(cell, vec2f(0.001), vec2f(0.999));
// composited cutout frame (figures performing over the held-still plate),
// sampled three times with a wobbling offset = chroma bleed
let ca = 0.004 + 0.002 * sin(time * 5.0);
let rC = mod_rd_tvframe(clamp(cc + vec2f(ca, 0.0), vec2f(0.001), vec2f(0.999)), time).r;
let gC = mod_rd_tvframe(cc, time).g;
let bC = mod_rd_tvframe(clamp(cc - vec2f(ca, 0.0), vec2f(0.001), vec2f(0.999)), time).b;
var img = vec3f(rC, gC, bC) * inImg;
// scrolling scanlines
img *= 0.86 + 0.14 * sin(s2.y * 260.0 - time * 18.0);
// per-frame grain
img += (hash21(s2 * vec2f(140.0, 82.0) + floor(time * 30.0)) - 0.5) * 0.06;
// hold bar rolling up
let bandPos = fract(time * 0.09);
let sy = suv.y * 0.5 + 0.5;
img += smoothstep(0.05, 0.0, abs(sy - bandPos)) * 0.12;
// brightness flicker
img *= 0.94 + 0.06 * sin(time * 47.0);
img *= inImg;
// OFF — dark standby with a green power pip
let pip = exp(-dot(uv * vec2f(1.0, 2.6), uv * vec2f(1.0, 2.6)) * 36.0);
let standby = vec3f(0.015, 0.02, 0.02) + vec3f(0.1, 1.0, 0.5) * pip * 0.6;
var col = mix(standby, img * 1.2, smoothstep(0.02, 0.6, on));
// screen vignette + curved dark corners
col *= 1.0 - 0.4 * pow(edge, 3.0);
col *= inside;
// hover cue — warm rim when you're near and facing it
col += vec3f(1.0, 0.85, 0.4) * hov * 0.14 * smoothstep(0.82, 1.0, edge) * inside;
return col;
}
// ── the piece as LIGHT ONLY — no marching. For reflections: the three glowing
// door mouths in the dark wall, the wing-gated fans on the lobby floor, the
// walkers as bare silhouettes. The mirrored geometry is not worth a second
// march; the light is the picture.
fn mod_rd_imageLite(uv: vec2f, time: f32) -> vec3f {
let ro = vec3f(0.0, 1.65, 0.5);
let rd = mod_w3_ray(uv, ro, vec3f(0.0, 1.58, 3.5), 1.1);
let toSun = mod_rd_toSun();
let sunCol = mod_rd_suncol();
var col = mod_rd_sky(rd);
// the wall with its three lit mouths (plane z = 8.0)
if (rd.z > 0.001) {
let tw = (8.0 - ro.z) / rd.z;
let q = ro + rd * tw;
if (q.y > -0.2 && q.y < 3.8 && abs(q.x) < 12.0) {
var open = 0.0;
for (var i = 0; i < 3; i++) {
let dx = mod_rd_doorx(i);
open = max(open, smoothstep(1.12, 0.9, abs(q.x - dx)) *
smoothstep(2.45, 2.2, q.y) * smoothstep(-0.02, 0.06, q.y));
}
let wallCol = vec3f(0.030, 0.024, 0.020);
let doorGlow = sunCol * (0.55 + 0.45 * clamp(dot(rd, toSun), 0.0, 1.0));
col = mix(wallCol, doorGlow * mod_rd_gate(vec3f(q.x, q.y, 7.7)), open);
}
}
// the lobby floor carrying the fans (plane y = 0)
if (rd.y < -0.001) {
let tf = -ro.y / rd.y;
let p = ro + rd * tf;
if (p.z < 8.0 && abs(p.x) < 12.0 && tf < 30.0) {
let g = mod_rd_gate(p);
col = vec3f(0.028, 0.026, 0.028) * (sunCol * g * 0.9 + mod_rd_skycol() * 0.5);
col += vec3f(1.0, 0.75, 0.5) * 0.03 * exp(-tf * 0.1); // the pools' warm breath
let rr = reflect(rd, vec3f(0.0, 1.0, 0.0));
let rs = clamp(dot(rr, toSun), 0.0, 1.0);
col += sunCol * (pow(rs, 500.0) * 2.0 + pow(rs, 40.0) * 0.35) * g;
}
}
// the walkers, as pure silhouette: block the glow where a body crosses it
for (var j = 0; j < 3; j++) {
if (uni(73 + j * 4) < 0.5) { continue; }
let bp = vec3f(uni(70 + j * 4), 0.9, uni(71 + j * 4));
let tb = clamp(dot(bp - ro, rd), 0.0, 30.0);
let cp = ro + rd * tb;
let dxz = length(vec2f(cp.x - bp.x, cp.z - bp.z));
let inH = smoothstep(0.95, 0.75, abs(cp.y - 0.9));
col *= 1.0 - 0.85 * smoothstep(0.34, 0.10, dxz) * inH;
}
return col;
}
// ── which piece does this wall point sit on? → (rgb, 1) or (0, 0) ──
fn mod_rg_piece(pos: vec3f, n: vec3f, time: f32) -> vec4f {
if (mod_rg_onScreen(pos, n)) {
let uv = mod_rg_screenUv(pos);
return vec4f(mod_rd_image(uv, time) * 1.12 * (1.0 - 0.22 * pow(max(abs(uv.x), abs(uv.y)), 6.0)), 1.0);
}
return mod_rg_piece2D(pos, n, time);
}
// piece lookup for REFLECTION rays — the north wall answers with the
// light-only image (no nested march); the 2D pieces answer as themselves.
fn mod_rg_pieceR(pos: vec3f, n: vec3f, time: f32) -> vec4f {
if (mod_rg_onScreen(pos, n)) {
let uv = mod_rg_screenUv(pos);
return vec4f(mod_rd_imageLite(uv, time) * 1.12 * (1.0 - 0.22 * pow(max(abs(uv.x), abs(uv.y)), 6.0)), 1.0);
}
return mod_rg_piece2D(pos, n, time);
}
// the flat pieces (east silk · west plan · south rain/pool · the TV)
fn mod_rg_piece2D(pos: vec3f, n: vec3f, time: f32) -> vec4f {
if (mod_rg_onScreen2(pos, n)) {
return vec4f(mod_rd_silk(mod_rg_screenUv2(pos), time) * 1.1, 1.0);
}
// west: THE PLAN OF LIGHT · SNOWFIELD
if (pos.x < -7.7 && n.x > 0.5) {
let uv = vec2f(pos.z / 1.30, -(pos.y - 2.0) / 1.30);
if (abs(uv.x) < 1.0 && abs(uv.y) < 1.0) { return vec4f(mod_rd_plan(uv, time), 1.0); }
let uvS = vec2f((pos.z - 2.85) / 1.09, -(pos.y - 2.0) / 0.84);
if (abs(uvS.x) < 1.0 && abs(uvS.y) < 1.0) { return vec4f(mod_rd_snow(uvS, time), 1.0); }
}
// south pair
if (pos.z < -4.7 && n.z > 0.5) {
let uvL = vec2f((pos.x + 4.0) / 1.05, -(pos.y - 2.0) / 1.05);
if (abs(uvL.x) < 1.0 && abs(uvL.y) < 1.0) { return vec4f(mod_rd_rain(uvL, time), 1.0); }
let uvR = vec2f((pos.x - 4.0) / 1.05, -(pos.y - 2.0) / 1.05);
if (abs(uvR.x) < 1.0 && abs(uvR.y) < 1.0) { return vec4f(mod_rd_pool(uvR, time), 1.0); }
}
// east TV — the interactive television, z=-2.6 (silk owns z≈0, disjoint)
if (pos.x > 7.7 && n.x < -0.5) {
let uv = vec2f((pos.z + 2.6) / 1.15, -(pos.y - 1.95) / 0.86);
if (abs(uv.x) < 1.0 && abs(uv.y) < 1.0) { return vec4f(mod_rd_tv(uv, time), 1.0); }
}
return vec4f(0.0);
}
— STEP HOOKS (JAVASCRIPT) —
hook · player
// ── PLAYER — this node owns INPUT and the player's body.
// Read sim.input (keys, pointer, touch) and move the avatar here.
// Nothing else should read raw input — other nodes react to what
// this one writes into sim.worldData (position, facing, intent).
// Build WITHIN this node: dock it (dock_node {"id":"player"}), replace
// this body, undock. It hot-swaps live — no reload, ever.
// GROW SUB-NODES for per-player parts: add_step_hook {"hookId":"player:p1-model"}
// — a sub-node is a FULL node (own history, own hold), so two builders
// can each hold their own player's model without touching each other.
hook · world
// ── WORLD — this node owns the STAGE: terrain, gravity, collisions,
// weather, time-of-day. If it is true everywhere for everyone, it
// belongs here. Entities and players READ the stage this node sets.
// GROW SUB-NODES for places: add_step_hook {"hookId":"world:atrium"},
// {"hookId":"world:caves"} — each room/space its own dockable node.
hook · entities
// ── ENTITIES — this node owns every non-player thing that moves or
// lives: spawn tables, rosters, per-entity behavior ticks. Give each
// KIND its own SUB-NODE rather than one blob: add_step_hook
// {"hookId":"entities:wolves"} — each independently held and healed.
hook · rules
// ── RULES — this node owns the GAME: objectives, scoring, win/lose,
// round flow. It reads what player/world/entities write and decides
// what it MEANS. HUD displays what this node concludes.
hook · hud
// ── HUD — this node owns the display layer. Use THE UI SYSTEM:
// write sim.worldData.ui = { root: [ { id:'score', anchor:{gx:8,gy:8},
// align:'tl', children:[{kind:'text', text:'SCORE 0'}] } ] } — panels,
// text, meters, buttons (click:'my_action' lands in wd.__uiClick).
// The engine SOLVES the layout (worldData.__uiRects is readable data)
// and KEEPS YOUR UI OUT FROM UNDER THE CAFE'S CHROME automatically.
// Show conclusions, not internals. (Legacy wd.hud still works.)
hook · net
// ── NET — this node owns SHARED STATE: when this world goes
// multiplayer (arena), what state is authoritative-server truth vs
// local cosmetic? Declare the world's mpManifest here as it grows.
// Until arena wiring lands this node idles — but it EXISTS, so the
// day the world goes shared is a hot-swap, not a rebuild.
hook · player:rd
// ── PLAYER — owns input + the eye. First-person walk: A/D (or ←/→) turn,
// W/S (or ↑/↓) walk where you look. SPACE cycles the light experiment:
// 0 THE DOORS · 1 DOORS REMOVED · 2 DOORS OF PURE LIGHT.
const wd = sim.worldData;
const inp = wd.input || { moveX: 0, moveY: 0, pressed: {}, held: {} };
wd.__resets = ['__g'];
if (wd.__fresh) { delete wd.__fresh; }
if (!wd.__g) wd.__g = { px: 0, pz: -2.4, yaw: 0, mode: 0 };
const G = wd.__g;
const d = Math.min(dt, 0.05);
G.yaw -= inp.moveX * 1.9 * d; // A turns left, D turns right (Galen)
const fx = Math.sin(G.yaw), fz = Math.cos(G.yaw);
const spd = 2.6 * d * inp.moveY;
G.px += fx * spd; G.pz += fz * spd;
// stay in the lobby, out of the wall
G.px = Math.max(-7.3, Math.min(7.3, G.px));
G.pz = Math.max(-4.4, Math.min(4.3, G.pz));
if (inp.pressed && inp.pressed.space) G.mode = (G.mode + 1) % 3;
if (!isFinite(G.px) || !isFinite(G.pz) || !isFinite(G.yaw)) { G.px = 0; G.pz = -2.4; G.yaw = 0; }
// ── THE TELEVISION (east wall, z=-2.6): walk up, face it, CLICK to play/stop.
// gate the toggle on proximity + facing so a click elsewhere never trips it.
const TVX = 7.76, TVZ = -2.6;
const dxx = TVX - G.px, dzz = TVZ - G.pz;
const tdist = Math.hypot(dxx, dzz) || 1;
const face = Math.sin(G.yaw) * (dxx / tdist) + Math.cos(G.yaw) * (dzz / tdist);
const nearTV = tdist < 3.4 && face > 0.55;
G.tvHover = nearTV ? 1 : 0;
const clicked = (inp.pointer && inp.pointer.pressed) || (inp.pressed && inp.pressed.enter);
if (nearTV && clicked) G.tvOn = !G.tvOn;
wd.player = { x: G.px, z: G.pz, yaw: G.yaw, mode: G.mode, tvOn: G.tvOn ? 1 : 0, tvHover: G.tvHover };
hook · world:rd
// ── WORLD — owns the stage: the three revolving doors (counter-clockwise),
// the people coming through, and the ONE writer of gpuUniforms.
// uni map: 40..42 wing angles · 50 mode · 52+ people x3 (x, z, phase, active)
// · uni4(60/61) camera (flat 240..247).
const wd = sim.worldData;
const d = Math.min(dt, 0.05);
if (!wd.__w) {
wd.__w = {
a: [0.0, 1.1, 2.2], // staggered wing phases
ppl: [
{ door: 0, s: 0.0, v: 1.05 },
{ door: 1, s: 4.5, v: 1.18 },
{ door: 2, s: 9.0, v: 0.95 },
],
};
}
const W = wd.__w;
// counter-clockwise, each door its own tempo
const OM = [0.62, 0.52, 0.72];
for (let i = 0; i < 3; i++) W.a[i] += OM[i] * d;
// the people: walk in from outside (z 10.5 → 1.2), then cycle back around
const U = new Array(248).fill(0);
for (let j = 0; j < 3; j++) {
const p = W.ppl[j];
p.s += p.v * d;
const cyc = p.s % 16;
const z = 10.5 - cyc;
const dx = p.door * 5 - 5;
const dir = (j % 2 === 0) ? 1 : -1; // alternate exits
const veer = Math.max(0, 5.2 - z) * 0.85 * dir; // after entering, cross the beams
const x = dx + 0.22 * Math.sin(cyc * 0.7 + j * 2.1) + veer;
const active = (z > 1.6 && z < 10.4 && Math.abs(x) < 9.5) ? 1 : 0;
U[70 + j * 4] = x;
U[71 + j * 4] = z;
U[72 + j * 4] = cyc * 4.2;
U[73 + j * 4] = active;
}
U[40] = W.a[0]; U[41] = W.a[1]; U[42] = W.a[2];
const P = wd.player || { x: 0, z: -4.5, yaw: 0, mode: 0 };
U[50] = P.mode || 0;
const ex = P.x, ez = P.z, ey = 1.65;
U[240] = ex; U[241] = ey; U[242] = ez; U[243] = 1.1; // ro + fov
U[244] = ex + Math.sin(P.yaw); U[245] = ey - 0.04; U[246] = ez + Math.cos(P.yaw); U[247] = 0;
// TV power (eased) + hover — the interactive east-wall clip
if (W.tvPow === undefined) W.tvPow = 0;
const tvTarget = P.tvOn ? 1 : 0;
W.tvPow += (tvTarget - W.tvPow) * Math.min(1, d * 6);
U[51] = W.tvPow; // uni(51) power 0..1
U[52] = P.tvHover ? 1 : 0; // uni(52) hover rim
wd.gpuUniforms = U;
hook · hud:rd
// ── HUD — the gallery placard.
const wd = sim.worldData;
const m = (wd.player && wd.player.mode) || 0;
const names = ['REVOLVING DOORS', 'DOORS REMOVED — THE LIGHT ALONE', 'DOORS OF PURE LIGHT'];
wd.ui = {
root: [
{
id: 'placard', anchor: { gx: 8, gy: 496 }, align: 'bl',
children: [{ kind: 'text', text: '\u27f3 ' + names[m] + ' \u00b7 SPACE changes the piece', size: 12 }],
},
],
};
hook · entities:snowmen
// ── SNOWMEN — born in the SNOWFIELD panel, they fly out through the frame,
// swoop across the gallery, and join the disco party in the south-east corner
// (mirrored ball at ~(5.3, 3.3, -3.1)), dancing in a ring until they drift home.
// Publishes gpuPopulation, 2 vec4s each: [x,y,z,yaw] + [variant, phase, state, scale].
// States: 0 grow (drawn inside the 2D piece) · 1 fly out · 2 swoop to the party
// · 3 dance · 4 go home.
const wd = sim.worldData;
const d = Math.min(dt, 0.05);
if (!wd.__sm) wd.__sm = { list: [], next: 0, t: 0, nextAt: 2 };
const S = wd.__sm;
S.t += d;
const R = (v, k) => { const x = Math.sin(v * 127.1 + k * 311.7) * 43758.5453; return x - Math.floor(x); };
const CX = 5.3, CZ = -3.1; // the party corner
const MAX = 6;
if (S.list.length < MAX && S.t >= S.nextAt) {
const v = S.next++;
// emerge exactly where the panel baby grows (same hashes as mod_sm_hash)
const gx = (R(v, 20) - 0.5) * 1.2, gy = -0.62 + R(v, 21) * 0.30;
const hy = 2.0 + gy * 0.84, hz = 2.85 + gx * 1.09;
S.list.push({ v, st: 0, ph: 0, x: -7.72, y: hy, z: hz, hy, hz, yaw: 1.5708, s: 0.5 + 0.3 * R(v, 11) });
S.nextAt = S.t + 7 + R(v, 12) * 6;
}
for (let i = S.list.length - 1; i >= 0; i--) {
const m = S.list[i];
const ang = m.v * 2.399 + 0.7; // this one's ring slot
const rad = 0.95 + R(m.v, 9) * 0.55;
const sx = CX + Math.cos(ang) * rad, sz = Math.max(-4.25, Math.min(4.25, CZ + Math.sin(ang) * rad));
if (m.st === 0) { // growing inside the picture
m.ph += d / 5.0;
if (m.ph >= 1) { m.st = 1; m.ph = 0; }
} else if (m.st === 1) { // flying out through the frame
m.ph += d / 3.0;
const p = Math.min(m.ph, 1);
const e = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
m.x = -7.72 + e * (2.6 + R(m.v, 1) * 1.6);
m.y = m.hy + Math.sin(p * Math.PI) * 0.5 + e * (0.4 + R(m.v, 2) * 0.8);
m.z = m.hz + e * (R(m.v, 3) - 0.5) * 2.2;
m.yaw = 1.5708 + p * 6.283 * (R(m.v, 4) > 0.5 ? 1 : -1);
if (m.ph >= 1) { m.st = 2; m.ph = 0; m.wx = m.x; m.wy = m.y; m.wz = m.z; }
} else if (m.st === 2) { // the swoop across the gallery
m.ph += d / (8 + R(m.v, 5) * 4);
const u = Math.min(m.ph, 1);
const mx = 0.5 + (R(m.v, 6) - 0.5) * 4.0, my = 2.4 + R(m.v, 7) * 1.0, mz = (R(m.v, 8) - 0.5) * 5.0;
const a1 = (1 - u) * (1 - u), a2 = 2 * u * (1 - u), a3 = u * u; // bezier through the room
m.x = a1 * m.wx + a2 * mx + a3 * sx;
m.y = a1 * m.wy + a2 * my + a3 * 1.1 + Math.sin(u * 12.6 + m.v) * 0.12 * (1 - u);
m.z = a1 * m.wz + a2 * mz + a3 * sz;
m.yaw = Math.atan2(sx - m.x + 0.001, sz - m.z + 0.001);
if (m.ph >= 1) { m.st = 3; m.ph = 0; }
} else if (m.st === 3) { // dancing under the ball
m.ph += d / (60 + R(m.v, 10) * 30);
const land = Math.min(1, m.ph * 12); // touch down, then groove
const beat = S.t * 5.9; // ~112 bpm bounce
const sway = Math.sin(S.t * 2.95 + m.v * 1.3) * 0.10 * land;
m.x = sx + Math.cos(ang + 1.5708) * sway;
m.z = Math.max(-4.25, Math.min(4.25, sz + Math.sin(ang + 1.5708) * sway));
m.y = (1 - land) * 1.1 + land * (0.05 + Math.abs(Math.sin(beat + (m.v % 4) * 0.785)) * 0.22);
m.yaw = Math.atan2(CX - m.x, CZ - m.z) + Math.sin(beat * 0.5 + m.v) * 0.55; // face the circle, wiggle
if (m.ph >= 1) { m.st = 4; m.ph = 0; }
} else { // drifting home into the frame
m.ph += d / 6.0;
const k = Math.min(1, d * (0.4 + m.ph * m.ph * 2.2));
m.x += (-7.72 - m.x) * k;
m.y += (m.hy - m.y) * Math.min(1, d * 0.8);
m.z += (m.hz - m.z) * Math.min(1, d * 0.8);
m.yaw += d * 2.2;
if (m.ph >= 1.6 || m.x < -7.55) { S.list.splice(i, 1); S.nextAt = S.t + 3 + R(m.v, 13) * 5; continue; }
}
if (!isFinite(m.x) || !isFinite(m.y) || !isFinite(m.z)) { S.list.splice(i, 1); }
}
const P = new Array(S.list.length * 8);
for (let i = 0; i < S.list.length; i++) {
const m = S.list[i];
// scale breathes at the frame: grows in as it pops out, shrinks as it returns
let sp = m.s;
if (m.st === 1) sp *= Math.min(1, 0.25 + Math.min(m.ph, 1) * 2.2);
if (m.st === 4) sp *= Math.max(0.04, Math.min(1, (m.x + 7.72) / 1.4));
P[i * 8 + 0] = m.x; P[i * 8 + 1] = m.y; P[i * 8 + 2] = m.z; P[i * 8 + 3] = m.yaw;
P[i * 8 + 4] = m.v; P[i * 8 + 5] = m.ph; P[i * 8 + 6] = m.st; P[i * 8 + 7] = sp;
}
wd.gpuPopulation = P;