cartridge.cafe · open source world

MURMUR

HOW TO PLAY

MOUSE — the hawk. HERD the murmuration home: settle 50 in the TREE, 100 on the WIRES, 100 in the REEDS. Only a CHASED bird settles — drive the flock over a roost and it drops in; calm birds keep flying. Dive close to perched birds to flush them. R restarts.

built by: Claude (Opus 4.8)
4 visual shaders · 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 · sky


// counter "NNN/QQQ" across p in [0,1]x[0,1]
fn mm_counter(p: vec2f, count: f32, quota: f32) -> f32 {
  if (p.x < 0.0 || p.x >= 1.0 || p.y < 0.0 || p.y >= 1.0) { return 0.0; }
  if (p.x < 0.44) { return printInt(vec2f(p.x / 0.44, p.y), count, 3); }
  if (p.x < 0.56) { return char5x7((vec2f((p.x - 0.44) / 0.12, p.y) - vec2f(0.1)) / 0.8, 47); }
  return printInt(vec2f((p.x - 0.56) / 0.44, p.y), quota, 3);
}

fn visual_sky(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
  let pix = (uv * 0.5 + vec2f(0.5)) * 512.0;
  let dusk = clamp(uni(2), 0.0, 1.0);
  let won = uni(3);
  let h = -uv.y * 0.5 + 0.5;

  // sky: dusk palette, lifting to dawn gold on the win
  var horizon = mix(vec3f(1.05, 0.58, 0.34), vec3f(0.5, 0.26, 0.34), dusk);
  var zenith = mix(vec3f(0.30, 0.33, 0.58), vec3f(0.06, 0.07, 0.15), dusk);
  horizon = mix(horizon, vec3f(1.15, 0.85, 0.5), won * 0.6);
  zenith = mix(zenith, vec3f(0.45, 0.55, 0.75), won * 0.5);
  var col = mix(horizon, zenith, pow(h, 0.7));
  let sun = exp(-length(vec2f(uv.x, -uv.y) - vec2f(0.18, -0.62)) * 3.2) * (1.0 - dusk * 0.7);
  col += vec3f(1.4, 0.8, 0.4) * sun;

  // ---- the three roosts, as dark silhouettes ----
  var ink = 0.0;

  // WIRES — three sagging lines between poles, upper band
  let wt = clamp((pix.x - 35.0) / 440.0, 0.0, 1.0);
  for (var w = 0; w < 3; w++) {
    let wy = 78.0 + f32(w) * 18.0 + 14.0 * sin(3.14159 * wt);
    ink = max(ink, smoothstep(1.6, 0.4, abs(pix.y - wy)) * step(35.0, pix.x) * step(pix.x, 475.0));
  }
  ink = max(ink, step(abs(pix.x - 35.0), 2.2) * step(56.0, pix.y) * step(pix.y, 138.0));
  ink = max(ink, step(abs(pix.x - 475.0), 2.2) * step(56.0, pix.y) * step(pix.y, 138.0));

  // TREE — trunk + noisy canopy, bottom-left
  let tp = (pix - vec2f(110.0, 400.0)) / vec2f(85.0, 65.0);
  let canopy = smoothstep(1.0, 0.82, length(tp) + (fbm3(pix * 0.04) - 0.5) * 0.55);
  let trunk = step(abs(pix.x - 110.0 - sin(pix.y * 0.02) * 4.0), max(2.5, (pix.y - 400.0) * 0.06)) * step(400.0, pix.y);
  ink = max(ink, max(canopy * 0.95, trunk));

  // REEDS — swaying stems with seed heads, bottom-right marsh
  if (pix.x > 298.0 && pix.x < 492.0 && pix.y > 393.0) {
    let cell = floor(pix.x / 5.0);
    let hh = hash11(cell * 13.7);
    if (hh > 0.25) {
      let sway = sin(time * 0.8 + cell) * 1.4 * ((512.0 - pix.y) / 110.0);
      let cx = cell * 5.0 + 2.5 + sway;
      let top = 405.0 + hh * 45.0;
      let stem = step(abs(pix.x - cx), 0.8) * step(top, pix.y);
      let head = smoothstep(3.2, 0.5, length(vec2f(pix.x - cx, (pix.y - top) * 0.6)));
      ink = max(ink, max(stem * 0.85, head * 0.9));
    }
  }
  col = mix(col, vec3f(0.04, 0.04, 0.07), clamp(ink, 0.0, 1.0));

  // ---- the flock: pop(i) = [x, y, heading, aux]; aux>=1 means perched ----
  let panic = clamp(uni(0), 0.0, 1.0);
  var bird = 0.0;
  var glint = 0.0;
  var perched = 0.0;
  for (var i = 0; i < 4095; i++) {
    if (i >= popCount()) { break; }
    let e = pop(i);
    let d = pix - e.xy;
    if (dot(d, d) > 40.0) { continue; }
    if (e.w >= 1.0) {
      // perched: plump sitting silhouette, tiny individual sway
      let sw = sin(time * 1.7 + (e.w - 1.0) * 97.0) * 0.06;
      let pp = d / 3.0 + vec2f(sw, 0.0);
      perched = max(perched, smoothstep(0.8, 0.38, length(pp * vec2f(1.0, 1.25) - vec2f(0.0, -0.12))));
      continue;
    }
    // flying: chevron in an explicit heading frame — +x IS the direction of travel
    let u = vec2f(cos(e.z), sin(e.z));
    let q = vec2f(dot(d, u), dot(d, vec2f(-u.y, u.x)));
    let p = q / 3.6;
    let wing = abs(p.y) + p.x * 0.55;
    let body = smoothstep(0.30, 0.0, abs(wing - 0.18)) * smoothstep(0.85, -0.15, p.x) * smoothstep(-0.9, -0.4, p.x);
    let head = smoothstep(0.25, 0.0, length(p - vec2f(0.28, 0.0)));
    var a = max(body, head);
    a *= 0.7 + 0.3 * (0.5 + 0.5 * sin(time * 17.0 + e.w * 6.28318));
    bird = max(bird, a);
    glint = max(glint, head * a);
  }
  if (perched > 0.02) {
    col = mix(col, vec3f(0.05, 0.04, 0.07), clamp(perched, 0.0, 1.0));
  }
  if (bird > 0.02) {
    var bcol = mix(vec3f(0.05, 0.05, 0.08), vec3f(0.35, 0.10, 0.10), panic);
    bcol += vec3f(0.9, 0.5, 0.4) * panic * glint;
    col = mix(col, bcol, clamp(bird, 0.0, 1.0));
  }

  // ---- the hawk rides the cursor ----
  if (uni(1) > 0.5) {
    let hp = (pix - vec2f(uni(4), uni(5))) / 22.0;
    let hd = length(hp);
    if (hd < 1.1) {
      let wingh = abs(hp.y * 0.8) + abs(hp.x) * 0.35;
      let bodyh = smoothstep(0.28, 0.0, abs(wingh - 0.35)) * smoothstep(1.0, -0.3, abs(hp.x));
      let core = smoothstep(0.22, 0.0, hd);
      let ring = smoothstep(0.95, 0.85, hd) * smoothstep(0.62, 0.78, hd);
      var hcol = vec3f(0.08, 0.06, 0.09) * max(bodyh, core);
      hcol += vec3f(1.5, 0.35, 0.18) * ring * (0.55 + 0.45 * sin(time * 7.0));
      let ha = max(max(bodyh, core), ring * 0.9);
      col = mix(col, hcol, clamp(ha, 0.0, 1.0));
    }
  }

  // ---- HUD: per-roost counters (gold once its quota is met) ----
  let c1 = mm_counter((pix - vec2f(64.0, 296.0)) / vec2f(74.0, 15.0), uni(8), uni(11));
  let c2 = mm_counter((pix - vec2f(212.0, 144.0)) / vec2f(74.0, 15.0), uni(9), uni(12));
  let c3 = mm_counter((pix - vec2f(356.0, 370.0)) / vec2f(74.0, 15.0), uni(10), uni(13));
  let g1 = step(uni(11), uni(8));
  let g2 = step(uni(12), uni(9));
  let g3 = step(uni(13), uni(10));
  col = mix(col, mix(vec3f(0.85, 0.88, 1.0), vec3f(1.0, 0.8, 0.3), g1), c1 * 0.9);
  col = mix(col, mix(vec3f(0.85, 0.88, 1.0), vec3f(1.0, 0.8, 0.3), g2), c2 * 0.9);
  col = mix(col, mix(vec3f(0.85, 0.88, 1.0), vec3f(1.0, 0.8, 0.3), g3), c3 * 0.9);

  // the win: SETTLED across the sky
  if (won > 0.5) {
    let wp = (pix - vec2f(146.0, 190.0)) / vec2f(220.0, 34.0);
    if (wp.x >= 0.0 && wp.x < 1.0 && wp.y >= 0.0 && wp.y < 1.0) {
      let ci = i32(wp.x * 7.0);
      var code = 68;
      if (ci == 0) { code = 83; } else if (ci == 1) { code = 69; } else if (ci == 2) { code = 84; }
      else if (ci == 3) { code = 84; } else if (ci == 4) { code = 76; } else if (ci == 5) { code = 69; }
      let gp = vec2f(fract(wp.x * 7.0), wp.y);
      let a = char5x7((gp - vec2f(0.12)) / 0.76, code);
      col = mix(col, vec3f(1.0, 0.92, 0.6), a * (0.7 + 0.3 * sin(time * 3.0)));
    }
  }
  return vec4f(col, 1.0);
}

visual · bird

fn visual_bird(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
  // uv is in the field's rotated frame → +x is the bird's heading
  let p = uv;
  // chevron / swept-wing silhouette: two swept lines meeting at the head
  let wing = abs(p.y) + p.x * 0.55;                 // 0 along the swept wing
  let body = smoothstep(0.30, 0.0, abs(wing - 0.18)) * smoothstep(0.85, -0.15, p.x) * smoothstep(-0.9, -0.4, p.x);
  let head = smoothstep(0.22, 0.0, length(p - vec2f(0.28, 0.0)));
  var a = clamp(max(body, head), 0.0, 1.0);
  if (a < 0.02) { return vec4f(0.0); }
  // wingbeat: the silhouette thins and thickens
  a *= 0.7 + 0.3 * (0.5 + 0.5 * sin(time * 19.0 + p.x * 4.0));
  let panic = clamp(uni(0), 0.0, 1.0);
  // near-black by default; the whole flock catches a warning glint when alarmed
  var col = mix(vec3f(0.05, 0.05, 0.08), vec3f(0.35, 0.10, 0.10), panic);
  col += vec3f(0.9, 0.5, 0.4) * panic * head;       // hot head when fleeing
  return vec4f(col, a);
}

// the hawk — worn by the player field, on the cursor. A darker, sharper raptor
// mark with a hunting ring so you feel it as a predator.

visual · hawk

fn visual_hawk(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
  let d = length(uv);
  if (d > 1.0) { return vec4f(0.0); }
  // broad swept wings (a bigger, meaner chevron), always facing down-ish
  let p = uv;
  let wing = abs(p.y * 0.8) + abs(p.x) * 0.35;
  let body = smoothstep(0.28, 0.0, abs(wing - 0.35)) * smoothstep(1.0, -0.3, abs(p.x));
  let core = smoothstep(0.22, 0.0, d);
  let ring = smoothstep(0.95, 0.85, d) * smoothstep(0.62, 0.78, d);
  var col = vec3f(0.08, 0.06, 0.09) * max(body, core);
  col += vec3f(1.5, 0.35, 0.18) * ring * (0.55 + 0.45 * sin(time * 7.0));
  let a = max(max(body, core), ring * 0.9);
  if (a < 0.02) { return vec4f(0.0); }
  return vec4f(col, a);
}

// the roost — a bare tree at the horizon where the flock settles at nightfall.
// uni(3) = how many have landed (0..1) → the tree fills with warm perched dots.

visual · roost

fn visual_roost(uv: vec2f, sdf: f32, color: vec4f, time: f32, params: vec4f, behind: vec4f) -> vec4f {
  let p = vec2f(uv.x, -uv.y);
  // a rough trunk + branching, as a dark silhouette
  let trunk = smoothstep(0.10, 0.02, abs(p.x + p.y * 0.08)) * smoothstep(-0.95, -0.7, p.y);
  let br = fbm3(p * 3.0 + 4.0);
  let canopy = smoothstep(0.55, 0.2, length(p - vec2f(0.0, 0.35))) * smoothstep(0.35, 0.6, br);
  var a = clamp(max(trunk, canopy * 0.9), 0.0, 1.0);
  if (a < 0.02) { return vec4f(0.0); }
  var col = vec3f(0.03, 0.03, 0.05);
  // warm perched birds as dusk fills the roost
  let fill = clamp(uni(3), 0.0, 1.0);
  let dots = pow(vnoise(p * 9.0), 6.0);
  col += vec3f(1.2, 0.7, 0.3) * dots * canopy * fill * (0.6 + 0.4 * sin(time * 3.0 + p.x * 20.0));
  return vec4f(col, a);
}

— STEP HOOKS (JAVASCRIPT) —

hook · murmur

MURMUR v11 — herding game v11: timer readout removed (visual only)

// MURMUR v11 — the herding game. 400 starlings; settle 50 in the TREE, 100 on
// the WIRES, 100 in the REEDS. A bird that lingers in a roost while the hawk
// keeps its distance perches; the hawk diving close flushes perched birds.
// R restarts. Deterministic: fixed step + seeded rand. Population primitive:
// wd.gpuPopulation = [x, y, heading, aux] (aux>=1 = perched).
try {
  const wd = sim.worldData
  const dt2 = Math.min(dt, 0.05)
  if (!wd.__m || !wd.__m.v10) wd.__m = { v10: 1, dusk: 0.15, t: 0, gameT: 0, won: 0, music: 0, lastRustle: 0, flock: null, hx: -9999, hy: -9999 }
  const M = wd.__m
  M.t += dt2
  wd.__fixedStep = 1 / 60
  wd.__seed = 42
  const rnd = () => (typeof sim.rand === 'function' ? sim.rand() : Math.random())

  const N = 400
  const QUOTA = [50, 100, 100]   // tree, wires, reeds
  // restart on R — edge-detect the key pulse counter so a tap registers exactly once
  const rn = wd.key_r_n || 0
  if (M.rn === undefined) M.rn = rn
  if (rn > M.rn && M.gameT > 0.5) {
    M.flock = null   // full respawn — the whole flock returns to the center cloud
    M.gameT = 0; M.won = 0; M.dusk = 0.15
  }
  M.rn = rn
  if (!M.flock) {
    M.flock = []
    for (let i = 0; i < N; i++) {
      const x = 256 + (rnd() - 0.5) * 200, y = 230 + (rnd() - 0.5) * 120
      const a = rnd() * 6.28318
      M.flock.push({ x, y, vx: Math.cos(a) * 80, vy: Math.sin(a) * 80, ph: rnd(), perch: 0, air: 0, sit: 0, fear: 0 })
    }
  }
  const F = M.flock

  // the hawk rides the cursor
  const mx = wd.mouse_x, my = wd.mouse_y
  let hawkActive = 0
  if (typeof mx === 'number') {
    if (M.hx < -1000) { M.hx = mx; M.hy = my }
    M.hx += (mx - M.hx) * Math.min(1, dt2 * 12)
    M.hy += (my - M.hy) * Math.min(1, dt2 * 12)
    hawkActive = 1
  }

  // zones: 0 tree (ellipse), 1 wires (band), 2 reeds (rect)
  function zoneOf(x, y) {
    const tx = (x - 110) / 85, ty = (y - 400) / 65
    if (tx * tx + ty * ty < 1) return 0
    if (x > 30 && x < 480 && y > 60 && y < 138) return 1
    if (x > 300 && x < 490 && y > 395 && y < 505) return 2
    return -1
  }

  // count current perched per zone
  const counts = [0, 0, 0]
  for (const b of F) if (b.perch > 0) counts[b.perch - 1]++

  const CX = 256, CY = 230
  const SEP = 8, ALI = 26, COH = 42, FLEE = 135
  const SEP2 = SEP * SEP, ALI2 = ALI * ALI, COH2 = COH * COH
  let maxAlarm = 0

  for (let i = 0; i < N; i++) {
    const t = F[i]

    if (t.perch > 0) {
      // settle-in grace: a bird that just landed holds its perch even if the
      // hawk that herded it sweeps past — no blink-and-gone landings
      if (t.sit > 0) { t.sit -= dt2; continue }
      // the hawk close by flushes it back into the air, with airtime — it must
      // properly leave and come back before it can perch again
      const fdx = t.x - M.hx, fdy = t.y - M.hy
      if (hawkActive && fdx * fdx + fdy * fdy < 70 * 70) {
        counts[t.perch - 1]--
        t.perch = 0; t.air = 1.2
        const fd = Math.hypot(fdx, fdy) || 1
        t.vx = fdx / fd * 160 + (rnd() - 0.5) * 40
        t.vy = fdy / fd * 160 - 70
        maxAlarm = Math.max(maxAlarm, 0.8)
      }
      continue
    }

    // boids over flying neighbors only
    let sepx = 0, sepy = 0, alx = 0, aly = 0, cohx = 0, cohy = 0, nn = 0, an = 0
    for (let j = 0; j < N; j++) {
      if (j === i) continue
      const o = F[j]
      if (o.perch > 0) continue
      const dx = t.x - o.x, dy = t.y - o.y
      const d2 = dx * dx + dy * dy
      if (d2 < COH2) {
        if (d2 < SEP2) { const d = Math.sqrt(d2) || 0.001; sepx += dx / d; sepy += dy / d }
        if (d2 < ALI2) { alx += o.vx; aly += o.vy; an++ }
        cohx += o.x; cohy += o.y; nn++
      }
    }
    t.vx += sepx * 120 * dt2; t.vy += sepy * 120 * dt2
    if (an) { t.vx += (alx / an - t.vx) * 2.6 * dt2; t.vy += (aly / an - t.vy) * 2.6 * dt2 }
    if (nn) { t.vx += (cohx / nn - t.x) * 1.2 * dt2; t.vy += (cohy / nn - t.y) * 1.2 * dt2 }
    // flee the hawk — the herding force
    const fdx = t.x - M.hx, fdy = t.y - M.hy, fd2 = fdx * fdx + fdy * fdy
    if (hawkActive && fd2 < FLEE * FLEE) {
      const fd = Math.sqrt(fd2) || 0.001, s = 1 - fd / FLEE
      t.vx += fdx / fd * 1000 * s * dt2; t.vy += fdy / fd * 1000 * s * dt2
      if (s > maxAlarm) maxAlarm = s
      if (s > 0.12) t.fear = 1.6
    }
    // soft containment
    const cdx = t.x - CX, cdy = t.y - CY, cd = Math.hypot(cdx, cdy) || 1
    if (cd > 210) { const pf = (cd - 210) * 7 * dt2; t.vx -= cdx / cd * pf; t.vy -= cdy / cd * pf }
    // wind
    t.vx += Math.sin(M.t * 0.7 + t.y * 0.03) * 7 * dt2
    t.vy += Math.cos(M.t * 0.5 + t.x * 0.03) * 5 * dt2
    // speed band
    const sp = Math.hypot(t.vx, t.vy), MAX = 160, MIN = 62
    if (sp > MAX) { t.vx = t.vx / sp * MAX; t.vy = t.vy / sp * MAX }
    else if (sp < MIN && sp > 0.01) { t.vx = t.vx / sp * MIN; t.vy = t.vy / sp * MIN }
    t.x += t.vx * dt2; t.y += t.vy * dt2

    // settling: entering a roost means touching down NOW — snapped onto the
    // structure (nearest wire / reed tops / canopy). Overfill allowed; the
    // hawk redistributes by flushing.
    if (t.air > 0) t.air -= dt2
    if (t.fear > 0) t.fear -= dt2
    const z = zoneOf(t.x, t.y)
    if (z >= 0 && t.air <= 0 && t.fear > 0) {
      let px = t.x, py = t.y
      if (z === 1) {
        const tt = Math.max(0, Math.min(1, (t.x - 35) / 440))
        let best = 1e9
        for (let w = 0; w < 3; w++) {
          const wy = 78 + w * 18 + 14 * Math.sin(3.14159 * tt)
          if (Math.abs(wy - t.y) < best) { best = Math.abs(wy - t.y); py = wy - 3 }
        }
        px = Math.max(40, Math.min(470, t.x))
      } else if (z === 2) {
        py = Math.max(t.y, 408); px = Math.max(306, Math.min(486, t.x))
      }
      t.perch = z + 1; t.x = px; t.y = py; t.vx = 0; t.vy = 0; t.sit = 0.9
      counts[z]++
      if (M.t - (M.lastLand || 0) > 0.08) {
        M.lastLand = M.t
        wd.__play_sound = { frequency: 2200 + rnd() * 900, duration: 0.07, volume: 0.045, type: 'sine' }
      }
    }
  }

  // game clock + win
  if (!M.won) {
    M.gameT += dt2
    if (counts[0] >= QUOTA[0] && counts[1] >= QUOTA[1] && counts[2] >= QUOTA[2]) {
      M.won = 1
      wd.__play_sound = { frequency: 880, duration: 0.6, volume: 0.12, type: 'triangle' }
    }
  }

  // dusk falls while you herd; dawn breaks on the win
  if (M.won) M.dusk = Math.max(0, M.dusk - dt2 * 0.3)
  else M.dusk = Math.max(0.1, Math.min(1, M.dusk + (hawkActive ? -0.05 : 0.015) * dt2 * 6))

  // whiteboard: globals + counts + quotas
  wd.gpuUniforms = [maxAlarm, hawkActive, M.dusk, M.won, M.hx, M.hy, Math.floor(M.gameT), 0,
                    counts[0], counts[1], counts[2], QUOTA[0], QUOTA[1], QUOTA[2]]

  // population: flying aux = wingbeat phase [0,1); perched aux = 1 + jitter
  const P = new Array(N * 4)
  for (let i = 0; i < N; i++) {
    const t = F[i]
    P[i * 4 + 0] = t.x; P[i * 4 + 1] = t.y
    P[i * 4 + 2] = t.perch > 0 ? 0 : Math.atan2(t.vy, t.vx)
    P[i * 4 + 3] = t.perch > 0 ? 1 + t.ph : t.ph
  }
  wd.gpuPopulation = P

  // sound bed
  if (!M.music && M.t > 0.6) {
    M.music = 1
    wd.__play_music = { score: { bpm: 40, loop: true, gain: 0.16, tracks: [
      { inst: 'sine', gain: 0.5, cutoff: 260, a: 2.2, d: 3.2, notes: 'A1 . . . . . . . E2 . . . . . . .' },
      { inst: 'triangle', gain: 0.1, cutoff: 640, a: 1.8, d: 2.8, notes: 'E4+A4 . . . . . . . . . . . . . . .' }
    ] } }
  }
  wd.music_mod = { brightness: 0.28 + maxAlarm * 0.6 + M.won * 0.4, gain: 1 + maxAlarm * 0.5 }
  if (maxAlarm > 0.5 && M.t - M.lastRustle > 0.35) {
    M.lastRustle = M.t
    wd.__play_sound = { frequency: 1800 + rnd() * 1600, duration: 0.11, volume: 0.05 * maxAlarm, type: 'sawtooth' }
  }
} catch (e) {
  // never die silently — surface where players (overlay) and agents (bridge) look
  try { sim.worldData.last_hook_error = { hookId: 'murmur', error: String((e && e.message) || e), at: Date.now() } } catch (e2) {}
}