Tessarune
Graveyard & Chapel · bone_pile.js

Bone pile

48×32 · 313 lines of JavaScript

A 48×32 floor prop: a heap of long bones, ribs, knuckle bones and two skulls, old dry bone rather than gore. Every bone is a signed-distance shape shaded from its own normal through the bone ramp, so the lit side is always top-left, and pieces are stacked back to front with a cool contact line where one tucks under another. “idle” (12 frames) breathes a faint green glow in the top skull’s eye sockets twice while a small bone rocks and settles; “rise” (10 frames, once) rattles the heap and lifts the top skull, jaw chattering and eyes flaring green, before it drops back with a puff of dust.

Bone pile — Rendered output6×
Rendered by this program, variant “rise” · 48×32 · 10 frames
Graveyard & ChapelGet the animated sprites: Graveyard & Chapel on itch.ioVol.4 · 12 props · 33 animations

The header. The comment at the top of the file is the brief: size, view, light direction and every variant.

bone_pile.js · 1–10
// Bone pile — 48x32 floor prop, top-down 3/4, key light top-left. A heap of long bones, ribs, knuckle bones and two// skulls; not gory, just old dry bone. Every bone is a signed-distance shape (a shaft with twin knobs at each end,// an arc for a rib, an oval for a vertebra) shaded from its own normal through the bone ramp n2 -> n5, so the lit// side is always top-left; the two skulls are hand-drawn masks. Pieces are stacked back to front: a piece that// sits behind another gets a cool n1 / n2 contact line where it tucks under, so the heap reads as separate bones.// Variants://   idle   loop 12f   — the top skull's eye sockets breathe a faint green glow twice; a small bone on the right//                       slope rocks and settles back with a click//   rise   once 10f   — the heap rattles, the top skull lifts, jaw chattering, eyes flare green, then it drops//                       back into the pile with a puff of dust; ends on idle f0

The last lines. submit declares the canvas and lists the variants the renderer exports.

bone_pile.js · 305–313
submit(BonePile, {  size: [48, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    { name: "idle", args: ["idle"] },    { name: "rise", args: ["rise"], animate: { fps: 10, duration: N_RISE / 10, loop: false } },  ],});
Show the whole program (313 lines)
bone_pile.js · 313 lines
// Bone pile — 48x32 floor prop, top-down 3/4, key light top-left. A heap of long bones, ribs, knuckle bones and two// skulls; not gory, just old dry bone. Every bone is a signed-distance shape (a shaft with twin knobs at each end,// an arc for a rib, an oval for a vertebra) shaded from its own normal through the bone ramp n2 -> n5, so the lit// side is always top-left; the two skulls are hand-drawn masks. Pieces are stacked back to front: a piece that// sits behind another gets a cool n1 / n2 contact line where it tucks under, so the heap reads as separate bones.// Variants://   idle   loop 12f   — the top skull's eye sockets breathe a faint green glow twice; a small bone on the right//                       slope rocks and settles back with a click//   rise   once 10f   — the heap rattles, the top skull lifts, jaw chattering, eyes flare green, then it drops//                       back into the pile with a puff of dust; ends on idle f0const GROUND = 29, N_IDLE = 12, N_RISE = 10;const LX = -0.62, LY = -0.5, LZ = 0.6;             // toward the light in screen space (x right, y down, z out)// ---- shapes -------------------------------------------------------------------------------------------------// each returns { d: signed distance (px, < 0 inside), nx, ny, nz } for a point, or null when well outsidefunction circleSD(px, py, cx, cy, r) {  const dx = px - cx, dy = py - cy, m = Math.hypot(dx, dy) || 1e-6;  const e = r - m, nz = Math.max(0, Math.min(1, e / r + 0.25));  const s = Math.sqrt(Math.max(0, 1 - nz * nz));  return { d: m - r, nx: (dx / m) * s, ny: (dy / m) * s, nz };}function capsuleSD(px, py, ax, ay, bx, by, r) {  const vx = bx - ax, vy = by - ay, L2 = vx * vx + vy * vy;  const k = Math.max(0, Math.min(1, ((px - ax) * vx + (py - ay) * vy) / L2));  return circleSD(px, py, ax + vx * k, ay + vy * k, r);}// a long bone: shaft + two knobs at each end (knobs sit across the shaft)function boneSD(px, py, b) {  const [ax, ay, bx, by] = b.p, r = b.r, kr = b.kr || r * 1.35;  const L = Math.hypot(bx - ax, by - ay), ux = (bx - ax) / L, uy = (by - ay) / L, qx = -uy, qy = ux;  let best = capsuleSD(px, py, ax + ux * kr, ay + uy * kr, bx - ux * kr, by - uy * kr, r);  for (const [ex, ey, s] of [[ax, ay, 1], [bx, by, -1]]) for (const side of [-1, 1]) {    const c = circleSD(px, py, ex + ux * kr * 0.6 * s + qx * side * kr * 0.62, ey + uy * kr * 0.6 * s + qy * side * kr * 0.62, kr);    if (c.d < best.d) best = c;  }  return best;}// a rib: an arc of radius R about (cx, cy) from angle a0 to a1 (radians, screen y down), thickness rfunction ribSD(px, py, b) {  const [cx, cy, R, a0, a1] = b.p, r = b.r;  let a = Math.atan2(py - cy, px - cx);  while (a < a0) a += TAU;  if (a > a1) {    const d0 = Math.abs(a - TAU - a0), d1 = Math.abs(a - a1);    a = d0 < d1 ? a0 : a1;  }  return circleSD(px, py, cx + Math.cos(a) * R, cy + Math.sin(a) * R, r * (1 - 0.35 * Math.abs((a - a0) / (a1 - a0) - 0.3)));}function ovalSD(px, py, b) {  const [cx, cy, rx, ry] = b.p;  const s = circleSD((px - cx) / rx, (py - cy) / ry, 0, 0, 1);  return { d: s.d * Math.min(rx, ry), nx: s.nx, ny: s.ny, nz: s.nz };}const SD = { bone: boneSD, rib: ribSD, oval: ovalSD };// ---- the heap, listed BACK to FRONT (layer 0 = back row, dimmer) ---------------------------------------------const PIECES = [  { k: "rib", p: [24, 21, 10, 3.75, 5.7], r: 0.95, layer: 0 },  { k: "bone", p: [8, 18, 22, 11], r: 1.2, layer: 0 },  { k: "bone", p: [26, 10, 41, 16], r: 1.2, layer: 0 },  { k: "oval", p: [14, 13, 1.7, 1.2], layer: 0 },  { k: "oval", p: [36, 12, 1.5, 1.1], layer: 0 },  { k: "bone", p: [4, 25, 18, 17], r: 1.3, layer: 1 },  { k: "bone", p: [29, 17, 44, 24], r: 1.3, layer: 1 },  { k: "rib", p: [15, 26, 6, 3.6, 5.3], r: 0.8, layer: 1 },  { k: "rib", p: [34, 27, 6, 4.0, 5.8], r: 0.8, layer: 1 },  { k: "bone", p: [37, 18, 41, 16], r: 0.75, kr: 1.0, layer: 1, id: "settle" },  { k: "oval", p: [24.5, 23.5, 2.2, 1.4], layer: 0 },  { k: "oval", p: [11, 21, 1.6, 1.2], layer: 0 },  { k: "oval", p: [37.5, 21, 1.6, 1.2], layer: 0 },  { k: "bone", p: [14, 24, 31, 15], r: 1.15, layer: 1 },  { k: "bone", p: [18, 15, 35, 24], r: 1.15, layer: 1 },  { k: "skull", id: "hero" },  { k: "skull", id: "side" },  { k: "bone", p: [15, 26, 36, 25], r: 1.4, kr: 1.8, layer: 2, id: "front" },  { k: "oval", p: [39, 26, 1.7, 1.2], layer: 2 },  { k: "bone", p: [40, 28, 45, 26], r: 0.75, kr: 1.0, layer: 2 },  { k: "bone", p: [3, 28, 9, 27], r: 0.75, kr: 1.0, layer: 2 },];// the heap's body: a dark mound of packed fragments behind every piece, so gaps read as crevices, not floorconst moundTop = (x) => 14 + ((x + 0.5 - 24) / 18) ** 2 * 12;const CHIPS = [[10, 21], [13, 18], [19, 16], [30, 15], [34, 19], [38, 21], [21, 24], [27, 24], [8, 25], [41, 25], [24, 20], [16, 21], [31, 21]];// ---- skulls: hand-drawn masks. 5..1 = n5..n1, 0 = n0, E = eye socket, N = nose, T/t = tooth / gapconst HERO = {  ox: 19, oy: 6,  cran: [    "...45543...",    "..4555443..",    ".455554432.",    ".454444332.",    "44EE434EE32",    "43EE3N3EE21",    ".3334N3322.",    "..TtTtTtT..",  ],  jaw: [    "..tTtTtTt..",    "..3443321..",    "...3322....",  ],  eyes: [[2, 4], [7, 4]],};const SIDE = {  ox: 5, oy: 18,   // an older skull at the front left, turned a little to the right, jaw long gone  cran: [    "..34553..",    ".3455543.",    "345554432",    "3454EE4E2",    "3443EE3E1",    ".33332N1.",    "..2t2t2..",  ],  jaw: [],  eyes: [],};const TONE = { 5: "n5", 4: "n4", 3: "n3", 2: "n2", 1: "n1", 0: "n0", T: "n5", t: "n1", N: "n0", E: "ink" };function skullAt(sk, dx, dy, jawDrop, x, y) {  const lx = x - sk.ox - dx, ly = y - sk.oy - dy;  const rows = sk.cran.length;  if (lx < 0 || lx > 11) return null;  if (ly >= 0 && ly < rows) {    const c = sk.cran[ly][lx];    if (c && c !== ".") return { c, jaw: false, lx, ly };  }  const jy = ly - rows - jawDrop;  if (jawDrop > 0 && ly >= rows && jy < 0 && lx >= 3 && lx <= 7) return { c: "0", jaw: false, mouth: true, lx, ly };  if (jy >= 0 && jy < sk.jaw.length) {    const c = sk.jaw[jy][lx];    if (c && c !== ".") return { c, jaw: true, lx, ly };  }  return null;}function boneKey(s, layer) {  // bone reads as three clean steps: a lit top-left lip, the body, a cool underside  const side = LX * s.nx + LY * s.ny;  let lv = s.nz > 0.8 ? 2 : side > 0.12 ? 3 : side > -0.3 ? 2 : 1;  if (s.nz < 0.45 && side < -0.3) lv = 0;  if (layer === 0) lv = Math.max(0, lv - 1);  return ["n2", "n3", "n4", "n5"][lv];}// ---- one frame: stack the pieces into an id buffer, then add the contact linesfunction buildHeap(st) {  const W = 48, H = 32, buf = new Array(W * H).fill(null);  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {    for (let i = PIECES.length - 1; i >= 0; i--) {      const pc = PIECES[i];      const off = st.off[i] || [0, 0];      if (pc.k === "skull") {        const sk = pc.id === "hero" ? HERO : SIDE;        const lift = pc.id === "hero" ? st.lift : 0;        const h = skullAt(sk, off[0], off[1] - lift, pc.id === "hero" ? st.jaw : 0, x, y);        if (!h) continue;        let key = TONE[h.c];        let eye = false;        if (h.c === "E") {          eye = true;          const e = sk.eyes.find(([ex, ey]) => h.lx >= ex && h.lx <= ex + 1 && h.ly >= ey && h.ly <= ey + 1);          if (e) key = eyeKey(h.lx - e[0], h.ly - e[1], st.eye);        }        buf[y * W + x] = { i, part: pc.id === "hero" ? (h.jaw ? "jaw" : "skull") : "skull2", key, eye, flat: true };        break;      }      let px = x + 0.5 - off[0], py = y + 0.5 - off[1];      if (pc.id === "settle" && st.tip) {        // rock about the lower (right-hand) knob        const a = st.tip, cx = pc.p[0], cy = pc.p[1], c = Math.cos(a), s = Math.sin(a);        const qx = px - cx, qy = py - cy;        px = cx + qx * c + qy * s; py = cy - qx * s + qy * c;      }      const s = SD[pc.k](px, py, pc);      if (s.d > 0) continue;      buf[y * W + x] = { i, part: "bones", key: boneKey(s, pc.layer), layer: pc.layer };      break;    }    if (!buf[y * W + x] && y >= moundTop(x) && y <= GROUND - 2 - (Math.abs(x + 0.5 - 24) > 15 ? 1 : 0)) {      let key = y > GROUND - 3 ? "n0" : "n1";      for (const [cx, cy] of CHIPS) {        const dx = x - cx, dy = y - cy;        if (dx >= 0 && dx <= 1 && dy === 0) key = dx === 0 ? "n3" : "n2";        else if (dx === 0 && dy === 1) key = "n0";      }      buf[y * W + x] = { i: -1, part: "bones", key, mound: true };    }  }  // contact lines: a piece tucked under a nearer one darkens where they meet (cast down-right = n1, else n2)  const out = buf.map((b) => (b ? { ...b } : null));  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {    const b = buf[y * W + x]; if (!b || b.eye) continue;    const at = (a, c) => (a < 0 || c < 0 || a >= W || c >= H ? null : buf[c * W + a]);    const nearer = (q) => q && q.i > b.i && q.part !== b.part || (q && q.i > b.i && q.part === "bones");    if (nearer(at(x, y - 1)) || nearer(at(x - 1, y))) out[y * W + x].key = "n1";    else if (nearer(at(x + 1, y)) || nearer(at(x, y + 1))) out[y * W + x].key = b.key === "n5" || b.key === "n4" ? "n3" : "n2";  }  return out;}// eye socket pixel (sx, sy in 0..1) at glow level 0 (dark) .. 4 (flare)function eyeKey(sx, sy, lv) {  if (lv <= 0) return sy === 0 ? "n0" : "ink";  if (lv === 1) return sy === 1 && sx === 1 ? "e0" : sy === 0 ? "n0" : "ink";  if (lv === 2) return sy === 1 ? (sx === 1 ? "e1" : "e0") : "ink";  if (lv === 3) return sy === 1 ? (sx === 1 ? "e3" : "e2") : sx === 1 ? "e1" : "e0";  return sy === 1 ? "e3" : "e2";}const PARTS = ["bones", "skull2", "skull", "jaw"];function drawHeap(g, st) {  const buf = buildHeap(st);  g.outlined("ink", () => {    for (const part of PARTS) g.part(part, () => g.field({ x: [0, 47], y: [0, 31] }, (x, y) => {      const b = buf[y * 48 + x];      return b && b.part === part ? b.key : null;    }));  });  return buf;}// ---- effects// green light spilling from the sockets: a dithered halo, and at a flare, sparks drifting upfunction eyeGlow(g, buf, st, t) {  if (st.eye < 3) return;  const lift = st.lift;  g.detached("glow", () => {    for (const [ex, ey] of HERO.eyes) {      const cx = HERO.ox + ex + 0.5, cy = HERO.oy + ey + 1 - lift;      const R = st.eye >= 4 ? 3.2 : 2.2;      for (let y = Math.floor(cy - R); y <= Math.ceil(cy + R); y++) for (let x = Math.floor(cx - R); x <= Math.ceil(cx + R); x++) {        if (x < 0 || y < 0 || x > 47 || y > 31) continue;        const b = buf[y * 48 + x];        if (b && b.part !== "bones") continue;               // only spill over the empty air and the bones around        const r = Math.hypot(x + 0.5 - cx, y + 0.5 - cy);        if (r < R && chk(x, y)) g.pixel(x, y, r < R * 0.55 ? "e2" : "e1");      }    }  });  if (st.eye >= 4) g.detached("sparks", () => {    for (let k = 0; k < 3; k++) {      const p = (t * 3 + k / 3) % 1;      const x = HERO.ox + [1, 5, 9][k] + Math.round(Math.sin(TAU * (p + k * 0.3))), y = HERO.oy + 2 - lift - Math.round(p * 6);      if (y >= 0 && p < 0.8) g.pixel(x, y, p < 0.4 ? "e3" : "e2");    }  });}const PUFF = [  [[0, 0, "n4"], [1, 0, "n4"], [2, 0, "n3"], [1, -1, "n5"]],  [[1, -2, "n4"], [0, -1, "n4"], [2, -1, "n5"], [3, -1, "n4"], [1, 0, "n3"], [3, 0, "n3"]],  [[1, -3, "n3"], [3, -2, "n4"], [0, -1, "n3"], [4, -1, "n3"]],];function puff(g, x, y, st, dir) {  if (st < 0 || st > 2) return;  for (const [a, b, k] of PUFF[st]) g.pixel(x + a * dir + dir * st, y + b, k);}// ---- statesfunction idleState(t) {  const f = Math.round(t * N_IDLE) % N_IDLE;  // two soft pulses per loop, the second one brighter (a small halo at its peak)  const eye = [0, 1, 2, 2, 1, 0, 1, 2, 3, 2, 1, 0][f];  const tip = { 4: -0.2, 5: -0.45, 6: -0.25 }[f] || 0;  return { f, eye, tip, lift: 0, jaw: 0, off: {} };}// rise keys: lift, jaw drop, eye level, rattleconst RISE = [  [0, 0, 0, 0],  [0, 0, 2, 1],   // the heap shivers, the eyes wake  [1, 1, 3, 2],  [3, 2, 4, 1],   // the skull lifts, jaw chattering, eyes flare  [5, 0, 4, 2],  [6, 2, 4, 1],  [4, 0, 4, 0],   // it drops  [0, 1, 3, 2],   // lands: the heap jolts, dust  [1, 0, 1, 1],   // a small bounce  [0, 0, 0, 0],   // == idle f0];const RATTLERS = [0, 2, 4, 7, 9, 12, 14];const BonePile = sprite(pal, (g, t, state) => {  contactShadow(g, 24, GROUND, 21, 2);  if (state === "idle") {    const st = idleState(t);    const buf = drawHeap(g, st);    eyeGlow(g, buf, st, t);    if (st.f === 7) g.detached("click", () => { g.pixel(42, 14, "n4"); g.pixel(43, 13, "n3"); g.pixel(36, 15, "n3"); });    return;  }  const f = Math.min(N_RISE - 1, Math.round(t * N_RISE));  const [lift, jaw, eye, rattle] = RISE[f];  const off = {};  if (rattle) RATTLERS.forEach((i, k) => { off[i] = [((k + f) % 2 ? 1 : -1) * (rattle > 1 || k % 2 ? 1 : 0), (k + f) % 3 === 0 ? -1 : 0]; });  const st = { eye, lift, jaw, tip: 0, off };  const buf = drawHeap(g, st);  eyeGlow(g, buf, st, t);  if (f === 7 || f === 8) {    // the skull lands: dust kicks out on both sides (gone again by the last frame, which is idle f0)    g.detached("dust", () => { puff(g, 16, 10, f - 7, -1); puff(g, 31, 10, f - 7, 1); });  }});submit(BonePile, {  size: [48, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    { name: "idle", args: ["idle"] },    { name: "rise", args: ["rise"], animate: { fps: 10, duration: N_RISE / 10, loop: false } },  ],});

These excerpts omit the boilerplate prepended by the asset pack build script: a single-line runtime header, the shared palette, and common helper utilities (contactShadow, litIndex, flame, hash, chk, TAU). The displayed line numbers match the original source files. Each program was authored by an AI coding agent and adjusted after visual inspection of the rendered frames.

Have your agent write programs like this.

Tessarune gives it the drawing API reference, 25 production manuals and a renderer to check its work against.