Tessarune
Witch's Hut · black_cat.js

Black cat

32×32 · 267 lines of JavaScript

A 32×32 floor prop: a witch’s black cat sitting on a small embroidered cushion of red velvet with gold braid and corner tassels. The fur is near-black with a violet rim light on the top-left edges; the eyes are yellow-green with slit pupils, and a red collar carries a brass bell. Four variants: “idle” (16 frames, the tail swishes and the cat blinks once), “groom” (12 frames, it licks a raised paw with its eyes shut), “hiss” (8 frames, once: it stands up stiff-legged with its fur bristling) and “sleep” (20 frames, curled up nose-to-tail, breathing).

Black cat — Rendered output6×
Rendered by this program, variant “groom” · 32×32 · 12 frames
Witch's HutGet the animated sprites: Witch's Hut on itch.ioVol.5 · 12 Halloween props · 42 animations

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

black_cat.js · 1–10
// Black cat — 32x32 floor prop, top-down 3/4, key light top-left.// A witch's black cat sitting on a small embroidered cushion (red velvet, gold braid, corner tassels). The fur is// near-black (n0 / v0) with a violet rim light on the top-left edges, yellow-green eyes with slit pupils, and a// red collar with a brass bell.// Variants://   idle   16f loop: the tail swishes, an ear twitches, one blink//   groom  12f loop: head tilted down, licks a raised paw (tongue flicks), eyes shut//   hiss   once, 8f: stands up on stiff legs, fur bristles, ears flatten, eyes flare, mouth opens on the fangs;//          settles back; the last frame equals idle frame 0//   sleep  20f loop: curled up nose-to-tail, breathing, a small 'z' drifts up

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

black_cat.js · 256–267
submit(BlackCat, {  size: [W, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "groom", args: ["groom"], animate: { fps: 10, duration: N_GROOM / 10, loop: true } },    { name: "hiss", args: ["hiss"], animate: { fps: 10, duration: N_HISS / 10, loop: false } },    { name: "sleep", args: ["sleep"], animate: { fps: 8, duration: N_SLEEP / 8, loop: true } },  ],});
Show the whole program (267 lines)
black_cat.js · 267 lines
// Black cat — 32x32 floor prop, top-down 3/4, key light top-left.// A witch's black cat sitting on a small embroidered cushion (red velvet, gold braid, corner tassels). The fur is// near-black (n0 / v0) with a violet rim light on the top-left edges, yellow-green eyes with slit pupils, and a// red collar with a brass bell.// Variants://   idle   16f loop: the tail swishes, an ear twitches, one blink//   groom  12f loop: head tilted down, licks a raised paw (tongue flicks), eyes shut//   hiss   once, 8f: stands up on stiff legs, fur bristles, ears flatten, eyes flare, mouth opens on the fangs;//          settles back; the last frame equals idle frame 0//   sleep  20f loop: curled up nose-to-tail, breathing, a small 'z' drifts upconst W = 32, CX = 16, GROUND = 30;const N_IDLE = 16, N_GROOM = 12, N_HISS = 8, N_SLEEP = 20;const FUR = ["n0", "n0", "n0", "v0"];// ---- cushion ------------------------------------------------------------------------------------------------const CU = { cx: 16, cy: 24, rx: 12, ry: 3.6 };function cushion(g) {  const { cx, cy, rx, ry } = CU;  g.part("cushion", () => {    // the plump front side, then the lit top face    g.field({ x: [cx - rx, cx + rx], y: [cy - 1, cy + ry + 3] }, (x, y) => {      const nx = (x + 0.5 - cx) / rx;      if (Math.abs(nx) > 1) return null;      const yb = cy + ry * Math.sqrt(1 - nx * nx) + 2.6 - 0.8 * nx * nx;      const yt = cy;      if (y + 0.5 < yt || y + 0.5 > yb) return null;      return nx < -0.55 ? "r1" : nx < 0.6 ? "r0" : "r0";    });    g.field({ x: [cx - rx, cx + rx], y: [cy - ry - 1, cy + ry + 1] }, (x, y) => {      const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry;      const r = nx * nx + ny * ny;      if (r > 1) return null;      if (r > 0.7 && ny > 0.3) return nx < -0.3 ? "g2" : "g1";       // gold braid along the front edge      const a = -0.6 * nx - 0.5 * ny;      return a > 0.45 ? "r2" : "r1";    });    // embroidered gold diamonds on the top face (mostly hidden under the cat; they show at the sides)    for (const [x, y] of [[7, 24], [25, 24]]) { g.pixel(x, y - 1, "g1"); g.pixel(x - 1, y, "g0"); g.pixel(x + 1, y, "g0"); g.pixel(x, y + 1, "g0"); }    // a seam along the side's bottom    g.line([cx - rx + 3, cy + ry + 2], [cx + rx - 3, cy + ry + 2], "w0");  });  g.part("tassels", () => {    for (const [x, s] of [[cx - rx - 1, -1], [cx + rx + 1, 1]]) {      g.pixel(x, cy + 1, "g2"); g.pixel(x, cy + 2, s < 0 ? "g2" : "g1");      g.pixel(x + s, cy + 3, "g1"); g.pixel(x, cy + 3, s < 0 ? "g3" : "g1"); g.pixel(x, cy + 4, "g0");    }  });}// ---- fur shading -------------------------------------------------------------------------------------------function furBlob(g, name, cx, cy, rx, ry, bias, inkEdge) {  g.part(name, () => g.field({ x: [Math.floor(cx - rx), Math.ceil(cx + rx)], y: [Math.floor(cy - ry), Math.ceil(cy + ry)] }, (x, y) => {    const i = litIndex(x, y, cx, cy, rx, ry, 4, bias || 0);    if (i < 0) return null;    // separation from a body part behind it: ink on the right/bottom boundary    if (inkEdge && (litIndex(x + 1, y, cx, cy, rx, ry, 4, 0) < 0 || litIndex(x, y + 1, cx, cy, rx, ry, 4, 0) < 0)) return "ink";    const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry;    const r = Math.sqrt(nx * nx + ny * ny);    // violet rim light: the top-left edge    if (r > 0.72 && -0.7 * nx - 0.7 * ny > 0.55) return "v1";    return FUR[i];  }));}// pose: { lift, puff, hx, hy (head offset), earL, earR (0 up .. 1 flat), eyes, mouth, tail:[...path], tailW, paw }function cat(g, p) {  const L = p.lift, P = p.puff;  const hx = 15 + p.hx, hy = 10 - L + p.hy;  g.outlined("ink", () => {    // tail first (behind the haunch on the right)    g.part("tail", () => g.sweep(p.tail, { w0: p.tailW, w1: p.tailW * 0.75 }, (u, v) => (v < -0.2 && u < 0.85 ? "v0" : u > 0.86 ? "v0" : "n0")));    if (P > 0.6) g.part("tailFluff", () => {      for (let i = 1; i < p.tail.length; i++) { const [x, y] = p.tail[i]; g.pixel(x + (i & 1 ? 2 : -2), y, "n0"); }    });    furBlob(g, "haunches", 16, 21 - L * 0.6, 7 + P, 4.6 + P * 0.4, 0.05);    furBlob(g, "chest", 15, 17 - L, 4.6 + P * 0.8, 4.6, 0.1);    if (P > 0.6) g.part("hackles", () => {      // bristling fur along the back/shoulders: a row of spikes the outline turns into a jagged silhouette      for (let k = 0; k < 6; k++) {        const a = Math.PI * (1.1 + k * 0.16);        const x = Math.round(16 + Math.cos(a) * (8 + P)), y = Math.round(21 - L * 0.6 + Math.sin(a) * (5.4 + P * 0.4));        g.pixel(x, y, k < 3 ? "v0" : "n0");      }    });    // front legs: stiff columns down to the cushion, the left one lit    g.part("legs", () => {      const top = 18 - L;      for (const [x0, lit] of [[12, true], [17, false]]) {        if (!lit && p.paw) continue;                     // raised for grooming (drawn separately)        for (let y = top; y <= 25; y++) {          g.pixel(x0, y, lit ? "v0" : "n0");          g.pixel(x0 + 1, y, lit ? "n0" : "n0");        }        g.pixel(x0, 25, lit ? "n1" : "v0"); g.pixel(x0 + 1, 25, "n0");   // paws: lit toes        g.pixel(x0 - 1, 25, "n0");      }      g.line([14, 20 - L], [14, 25], "ink");            // gap between the legs    });    g.part("head", () => {      furBlob(g, "skull", hx, hy, 5.4, 4.3, 0.12);      // ears: lean back as earX -> 1      const ear = (s, f) => {        const base0 = [hx + s * 5, hy - 0.5 + f * 1.5], base1 = [hx + s * 1.5, hy - 3.2 + f];        const tip = [hx + s * (4 + f * 3), hy - 6.5 + f * 4.5];        g.fillPoly([base0, tip, base1].map(([a, b]) => [Math.round(a), Math.round(b)]), s < 0 ? "v0" : "n0");        const ix = Math.round(hx + s * (3.4 + f * 1.6)), iy = Math.round(hy - 3.6 + f * 2.4);        g.pixel(ix, iy, "r0"); if (f < 0.5) g.pixel(ix, iy + 1, "r0");        if (s < 0) g.pixel(Math.round(tip[0]), Math.round(tip[1]), "v1");      };      ear(-1, p.earL); ear(1, p.earR);    });  });  face(g, hx, hy, p);  g.part("collar", () => {    const cy = hy + 4;    for (let x = hx - 3; x <= hx + 3; x++) g.pixel(x, cy + (Math.abs(x - hx) > 2 ? -1 : 0), x < hx ? "r2" : "r1");    if (!p.paw) { g.pixel(hx, cy + 1, "g2"); g.pixel(hx - 1, cy + 1, "g3"); g.pixel(hx, cy + 2, "g0"); }  });  g.part("chestTuft", () => { g.pixel(hx, hy + 7 - (p.paw ? 1 : 0), "n1"); g.pixel(hx - 1, hy + 8, "v0"); });  g.outlined("ink", () => {    if (p.paw) g.part("pawRaised", () => {      // the right forepaw lifted to the mouth (drawn in front of the chest)      const [px, py] = p.paw;      g.rect({ x: [px, px + 1], y: [py, 21] }, "v0", { fill: "v0" });      g.line([px + 2, py], [px + 2, 21], "ink");           // the foreleg's shaded edge against the chest      g.line([px - 1, py + 1], [px - 1, 21], "ink");      g.rect({ x: [px - 1, px + 1], y: [py - 1, py] }, "n0", { fill: "n0" });      g.pixel(px - 1, py - 1, "v1"); g.pixel(px, py - 1, "n1"); g.pixel(px + 1, py - 1, "v0");      g.line([px - 1, py + 1 - 0], [px - 1, py + 1], "ink"); g.pixel(px + 2, py - 1, "ink"); g.pixel(px + 2, py, "ink");    });  });}function face(g, hx, hy, p) {  g.part("eyes", () => {    for (const s of [-1, 1]) {      const x0 = hx + (s < 0 ? -4 : 2), y = hy;      if (p.eyes === "closed") { g.line([x0, y + 1], [x0 + 2, y + 1], "n2"); g.pixel(x0 + 1, y, "n0"); continue; }      if (p.eyes === "half") { g.line([x0, y], [x0 + 2, y], "n0"); g.line([x0, y + 1], [x0 + 2, y + 1], "e2"); g.pixel(x0 + 1, y + 1, "ink"); continue; }      if (p.eyes === "wide") {        g.rect({ x: [x0, x0 + 2], y: [y - 1, y + 1] }, "e3", { fill: "e3" });        g.pixel(x0, y - 1, "g3"); g.pixel(x0 + 1, y - 1, "g3");        g.pixel(x0 + 1, y, "ink");                       // a pinpoint pupil        continue;      }      g.pixel(x0, y, s < 0 ? "g3" : "e3"); g.pixel(x0 + 1, y, "ink"); g.pixel(x0 + 2, y, "e3");      g.pixel(x0, y + 1, "e2"); g.pixel(x0 + 1, y + 1, "ink"); g.pixel(x0 + 2, y + 1, "e1");    }  });  g.part("muzzle", () => {    g.pixel(hx, hy + 2, "r0");                           // nose    if (p.mouth === "hiss") {      g.rect({ x: [hx - 1, hx + 1], y: [hy + 3, hy + 4] }, "r1", { fill: "r1" });      g.pixel(hx - 1, hy + 3, "n5"); g.pixel(hx + 1, hy + 3, "n5"); g.pixel(hx, hy + 4, "r0");    } else if (p.mouth === "lick") {      g.pixel(hx - 1, hy + 3, "r2"); g.pixel(hx - 1, hy + 4, "r3");    } else {      g.pixel(hx - 1, hy + 3, "n1"); g.pixel(hx + 1, hy + 3, "n1");    }  });  g.detached("whiskers", () => {    g.line([hx - 7, hy + 2], [hx - 6, hy + 2], "n3"); g.pixel(hx - 7, hy + 3, "n2");    g.line([hx + 6, hy + 2], [hx + 7, hy + 2], "n2");  });}// ---- poses --------------------------------------------------------------------------------------------------function idlePose(t) {  const sw = Math.sin(TAU * t), sw2 = Math.sin(TAU * (t + 0.12));  const f = Math.round(t * N_IDLE) % N_IDLE;  return {    lift: 0, puff: 0, hx: 0, hy: 0,    earL: 0, earR: f === 5 || f === 6 ? 0.5 : 0,           // the right ear flicks    eyes: f === 10 || f === 12 ? "half" : f === 11 ? "closed" : "open",    mouth: "", tailW: 1.3,    tail: [[21, 24], [24, 25], [26.5, 23.5], [27.5 + sw * 0.6, 20.5], [27 + sw2 * 2, 17.5]],  };}function groomPose(t) {  const f = Math.round(t * N_GROOM) % N_GROOM;  const bob = Math.round(0.5 + 0.5 * Math.sin(TAU * 2 * t));     // two licks per loop  const p = idlePose(0);  p.hx = -1; p.hy = 1 + bob; p.eyes = "closed"; p.mouth = bob ? "" : "lick";  p.paw = [16, 15 + (1 - bob)];  p.tail = [[21, 24], [24, 25], [26.5, 24], [28, 22.5 + Math.sin(TAU * t)]];  return p;}const HISS = [  // lift, puff, ears, eyes, mouth  [1, 0.4, 0.4, "wide", ""],  [2, 1.0, 1.0, "wide", "hiss"],  [3, 1.5, 1.0, "wide", "hiss"],  [3, 1.6, 1.0, "wide", "hiss"],  [2, 1.0, 0.8, "wide", "hiss"],  [1, 0.5, 0.4, "open", ""],  [0, 0.2, 0.2, "open", ""],];function hissPose(t) {  const f = Math.min(N_HISS - 1, Math.round(t * N_HISS));  if (f === N_HISS - 1) return idlePose(0);  const [lift, puff, ears, eyes, mouth] = HISS[f];  const p = idlePose(0);  Object.assign(p, { lift, puff, earL: ears, earR: ears, eyes, mouth });  // tail goes up like a bottle brush  const up = Math.min(1, puff / 1.2);  p.tail = [[21, 24], [24 - up, 24 - up * 3], [26 - up, 22 - up * 5], [27 - up * 0.5, 19 - up * 7], [27 + up * 0.5, 17.5 - up * 8]];  p.tailW = 1.3 + puff * 0.7;  return p;}// ---- sleep: curled up nose-to-tail --------------------------------------------------------------------------function sleep(g, t) {  const br = 0.5 - 0.5 * Math.cos(TAU * t);                      // 0 exhaled .. 1 inhaled  const ry = 4.3 + br * 0.9, cy = 26 - ry;  g.outlined("ink", () => {    furBlob(g, "body", 17.5, cy, 8.5, ry, 0.05);    g.part("tail", () => g.sweep([[25, 23.5], [23, 25.6], [17, 26.2], [11, 25.6]], { w0: 1.4, w1: 1.1 }, (u, v) => (v < 0 ? "v0" : "n0")));  });  g.outlined("ink", () => {    furBlob(g, "head", 10, 22.2, 4.4, 3.5, 0.15, true);    g.part("ears", () => {      g.fillPoly([[7, 20], [7, 17], [9, 19]], "v0"); g.pixel(7, 17, "v1");      g.fillPoly([[11, 19], [13, 17], [13, 20]], "n0"); g.pixel(12, 19, "r0");    });  });  g.part("sleepFace", () => {    g.line([7, 23], [8, 23], "n2"); g.line([11, 23], [12, 23], "n2");   // shut eyes    g.pixel(10, 24, "r0");    g.pixel(9, 25, "n1"); g.pixel(10, 25, "v0"); g.pixel(12, 26, "n1"); g.pixel(13, 26, "v0");   // tucked forepaws  });  g.part("collar", () => { g.pixel(13, 25, "r2"); g.pixel(14, 25, "r1"); g.pixel(14, 26, "g2"); });  g.part("spine", () => {    // a faint ridge of rim light along the back, rising and falling with the breath    const yy = Math.round(cy - ry + 1);    g.line([14, yy], [19, yy], "v1"); g.pixel(20, yy, "v0");  });  g.detached("zzz", () => {    const p = t;    if (p > 0.85) return;    const x = Math.round(6 + p * 4 + Math.sin(TAU * p) * 1), y = Math.round(14 - p * 12);    const k = p < 0.55 ? "n4" : "n3";    for (const [dx, dy] of [[0, 0], [1, 0], [2, 0], [3, 0], [2, 1], [1, 2], [0, 3], [1, 3], [2, 3], [3, 3]]) g.pixel(x + dx, y + dy, k);  });}const BlackCat = sprite(pal, (g, t, mode) => {  t = ((t % 1) + 1) % 1;  contactShadow(g, CX, GROUND, 13, 1);  cushion(g);  if (mode === "sleep") return sleep(g, t);  cat(g, mode === "groom" ? groomPose(t) : mode === "hiss" ? hissPose(t) : idlePose(t));});submit(BlackCat, {  size: [W, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "groom", args: ["groom"], animate: { fps: 10, duration: N_GROOM / 10, loop: true } },    { name: "hiss", args: ["hiss"], animate: { fps: 10, duration: N_HISS / 10, loop: false } },    { name: "sleep", args: ["sleep"], animate: { fps: 8, duration: N_SLEEP / 8, loop: true } },  ],});

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.