cartridge.cafe · open source world

VESTIGIA Mall

HOW TO PLAY

Wander the VESTIGIA mall with WASD / arrows (or drag). Drift near a storefront and it blooms — its neon warms and its name + a small rite appear at the bottom. No score, no timer, no wrong move. Presence is enough. Stay as long as you need.

built by: cafe house AI
1 visual shader · 0 shader modules · 1 step hook · 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 · mall

fn h21(p:vec2f)->f32{ return fract(sin(dot(p,vec2f(127.1,311.7)))*43758.5453); }
fn sdb(p:vec2f,b:vec2f)->f32{ let d=abs(p)-b; return length(max(d,vec2f(0.0)))+min(max(d.x,d.y),0.0); }
fn sdrb(p:vec2f,b:vec2f,r:f32)->f32{ return sdb(p,b-vec2f(r))-r; }
fn s_pos(i:i32)->vec2f{ var a=array<vec2f,7>(vec2f(110.0,80.0),vec2f(256.0,72.0),vec2f(402.0,80.0),vec2f(74.0,256.0),vec2f(438.0,256.0),vec2f(150.0,432.0),vec2f(362.0,432.0)); return a[i]; }
fn s_col(i:i32)->vec3f{ var a=array<vec3f,7>(vec3f(1.0,0.15,0.5),vec3f(1.0,0.7,0.2),vec3f(0.35,1.0,0.35),vec3f(1.0,0.78,0.5),vec3f(0.72,0.55,1.0),vec3f(0.6,0.42,1.0),vec3f(0.4,0.95,1.0)); return a[i]; }
fn visual_mall(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
  var col = vec3f(0.0);
  let gc = floor(uv*5.0);
  let s = gc.x+gc.y;
  let ch = s - 2.0*floor(s*0.5);
  col = mix(vec3f(0.05,0.08,0.12), vec3f(0.09,0.06,0.11), ch);
  let spk = h21(floor(uv*44.0));
  col += vec3f(0.03,0.06,0.07)*step(0.86, spk);
  col += vec3f(0.06,0.09,0.13)*exp(-dot(uv,uv)*1.1);
  for(var i=0;i<7;i++){
    let sp = (s_pos(i)-256.0)/256.0;
    let sc = s_col(i);
    let bl = uni(10+i);
    let q = uv - sp;
    let bx = sdrb(q, vec2f(0.205,0.15), 0.045);
    let fill = smoothstep(0.008,-0.02, bx);
    col = mix(col, sc*0.12*(0.6+1.6*bl), fill*0.65);
    let bord = smoothstep(0.016,0.0, abs(bx)-0.005);
    col += sc*bord*(0.45+3.0*bl)*(0.85+0.15*sin(time*3.0+f32(i)*1.7));
    col += sc*bl*bl*exp(-max(bx,0.0)*7.0)*0.9;
    let em = length(q)-0.045;
    col += sc*smoothstep(0.01,-0.01, em)*(0.5+1.4*bl);
  }
  let pl = (vec2f(uni(0),uni(1))-256.0)/256.0;
  let d = length(uv-pl);
  let pulse = 0.85+0.15*sin(time*2.2);
  col += vec3f(1.0,0.82,0.55)*exp(-d*d*20.0)*1.3*pulse;
  col += vec3f(0.7,0.85,1.0)*exp(-d*d*90.0)*0.7;
  col = mix(col, vec3f(1.0,0.97,0.9), smoothstep(0.05,0.024,d)*pulse);
  col *= 0.92+0.08*sin(uv.y*200.0);
  col *= 1.0-0.25*dot(uv,uv);
  return vec4f(col, 1.0);
}

— STEP HOOKS (JAVASCRIPT) —

hook · mall

presence orb movement + storefront bloom + hud
const wd=sim.worldData;
const inp=wd.input||{};
const on=(n)=>!!wd['key_'+n];
let mx=(inp.moveX!==undefined?inp.moveX:0);
let my=(inp.moveY!==undefined?inp.moveY:0);
if(!mx&&!my){ mx=((on('d')||on('right'))?1:0)-((on('a')||on('left'))?1:0); my=((on('w')||on('up'))?1:0)-((on('s')||on('down'))?1:0); }
if(!wd.__m)wd.__m={x:256,y:256};
const M=wd.__m;
const spd=240*dt;
M.x=Math.max(30,Math.min(482,M.x+mx*spd));
M.y=Math.max(30,Math.min(482,M.y-my*spd));
const pdn=(inp.pointer&&inp.pointer.down)||!!wd['mouse_down'];
if(pdn){const px=(inp.pointer&&inp.pointer.x!==undefined)?inp.pointer.x:(wd['mouse_x']||256);const py=(inp.pointer&&inp.pointer.y!==undefined)?inp.pointer.y:(wd['mouse_y']||256);const dx=px-M.x,dy=py-M.y,L=Math.hypot(dx,dy);if(L>6){M.x=Math.max(30,Math.min(482,M.x+dx/L*spd));M.y=Math.max(30,Math.min(482,M.y+dy/L*spd));}}
const S=[[110,80],[256,72],[402,80],[74,256],[438,256],[150,432],[362,432]];
const names=['💋 GLAM//RUIN BOOT CO.','📚 PALIMPSEST BOOK REPAIR','🐸 DEBUG FROG ARCADE','☕ SOFT THRESHOLD CAFÉ','📼 STATIC//CHOIR','📖 MARGIN//RITE','📸 PHOTOBOOTH'];
const tags=['clearance relics ➡ sacred stompers','Margins welcome','Tiny counts','As long as you need','hum along, or don’t','You’re not behind; in the footnotes','four frames, one you'];
if(!wd.__bl)wd.__bl=[0,0,0,0,0,0,0];
let near=-1,nd=1e9;
for(let i=0;i<7;i++){const dx=S[i][0]-M.x,dy=S[i][1]-M.y,ds=Math.hypot(dx,dy);const tg=Math.max(0,Math.min(1,(175-ds)/115));wd.__bl[i]+=(tg-wd.__bl[i])*Math.min(1,dt*3.5);if(ds<nd){nd=ds;near=i;}}
const U=new Array(17).fill(0);U[0]=M.x;U[1]=M.y;U[2]=near;for(let i=0;i<7;i++)U[10+i]=wd.__bl[i];
wd.gpuUniforms=U;
const hud=[{id:'ttl',type:'text',x:'4%',y:'5%',fontSize:'16px',color:'#93c7d6',text:'✦ VESTIGIA MALL'},{id:'sub',type:'text',x:'4%',y:'9%',fontSize:'11px',color:'#5d6f7e',text:'underhosted · bigger inside · spaces bloom with attention'},{id:'hint',type:'text',x:'4%',y:'94%',fontSize:'11px',color:'#4f6070',text:'WASD / arrows / drag — wander. presence is enough.'}];
const nb=near>=0?wd.__bl[near]:0;
if(nb>0.5){hud.push({id:'card',type:'text',x:'6%',y:'87%',fontSize:'20px',color:'#ffffff',text:names[near]});if(tags[near])hud.push({id:'tag',type:'text',x:'6%',y:'91%',fontSize:'13px',color:'#d7c6ee',text:tags[near]});}
wd.hud=hud;