Tessarune
Witch's Hut · owl_perch.js

Owl perch

32×48 · 273 lines of JavaScript

A 32×48 tall floor prop: a witch’s tawny owl with a grey facial disc and amber eyes on a gnarled wooden T-perch standing on three root-like feet, with a little brass dish on an iron bracket and a mouse tail curling over its rim. “idle” (16 frames) breathes, blinks slowly and ruffles its feathers once; “look” (10 frames, once) swivels the head right and back; “hoot” (8 frames, once) puffs up and opens the beak as two sound arcs ripple out; “night” (16 frames) drops the plumage into shadow while the eyes glow green-gold.

Owl perch — Rendered output6×
Rendered by this program, variant “hoot” · 32×48 · 8 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.

owl_perch.js · 1–8
// Owl perch — 32x48 floor prop (tall), top-down 3/4, key light top-left.// A witch's tawny owl with a grey facial disc and amber eyes sits on a gnarled wooden T-perch that stands on// three root-like feet. A little brass dish hangs off the post on an iron bracket (a mouse tail curls over its rim).// Variants://   idle   16f loop  breathes, blinks slowly, ruffles its feathers once//   look   10f once  the head swivels to the right and back; ends on idle frame 0//   hoot    8f once  puffs up, opens the beak, two sound arcs ripple out; ends on idle frame 0//   night  16f loop  in the dark: the plumage falls into shadow, the eyes glow green-gold, it blinks

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

owl_perch.js · 262–273
submit(OwlPerch, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: 16, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "look", args: ["look"], animate: { fps: 10, duration: 1.0, loop: false } },    { name: "hoot", args: ["hoot"], animate: { fps: 10, duration: 0.8, loop: false } },    { name: "night", args: ["night"] },  ],});
Show the whole program (273 lines)
owl_perch.js · 273 lines
// Owl perch — 32x48 floor prop (tall), top-down 3/4, key light top-left.// A witch's tawny owl with a grey facial disc and amber eyes sits on a gnarled wooden T-perch that stands on// three root-like feet. A little brass dish hangs off the post on an iron bracket (a mouse tail curls over its rim).// Variants://   idle   16f loop  breathes, blinks slowly, ruffles its feathers once//   look   10f once  the head swivels to the right and back; ends on idle frame 0//   hoot    8f once  puffs up, opens the beak, two sound arcs ripple out; ends on idle frame 0//   night  16f loop  in the dark: the plumage falls into shadow, the eyes glow green-gold, it blinksconst W = 32, H = 48, GROUND = 45;const OX = 15;                                   // owl centre columnconst L = [-0.74, -0.67];const WOOD = ["w0", "w1", "w2", "w3", "w4"];const BAYER = [0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5];const dith = (x, y, d) => BAYER[(y & 3) * 4 + (x & 3)] / 16 < d;// night: every plumage/wood/brass key falls two steps into shadow (the eyes are exempt)const NIGHT = { w4: "w2", w3: "w1", w2: "w1", w1: "w0", w0: "n0", n5: "n3", n4: "n2", n3: "n2", n2: "n1", n1: "n0", n0: "n0",  g3: "g1", g2: "g0", g1: "g0", g0: "w0" };let NIGHT_ON = false;const K = (k) => (NIGHT_ON && NIGHT[k]) || k;function limb(g, path, w0, w1, opt = {}) {  const a = path[0], b = path[path.length - 1];  const dx = b[0] - a[0], dy = b[1] - a[1], len = Math.hypot(dx, dy) || 1;  const nl = (-dy / len) * L[0] + (dx / len) * L[1];  g.sweep(path, { w0, w1, wobble: opt.wobble ?? 0.2, wobblePhase: opt.ph ?? 0.5 }, (u, v) => {    let l = v * nl * 1.3 + 0.1 - (opt.dark || 0);    if (opt.grain) { const s = u * opt.grain + v * 0.4; if (s - Math.floor(s) < 0.18) l -= 0.5; }    return K(WOOD[l > 0.85 ? 4 : l > 0.15 ? 3 : l > -0.4 ? 2 : l > -0.85 ? 1 : 0]);  });}// ---- the perch -----------------------------------------------------------------------------------------------function perch(g) {  g.part("feet", () => {    limb(g, [[16, 41], [12, 43], [9, 44], [6, 45]], 1.7, 0.6, { ph: 0.3 });    limb(g, [[16, 41], [20, 43], [23, 44], [26, 45]], 1.7, 0.6, { ph: 1.7, dark: 0.15 });    limb(g, [[16, 41], [17, 43], [18, 44]], 1.3, 0.7, { ph: 2.4, dark: 0.3 });  });  g.part("post", () => {    limb(g, [[16, 42], [15, 38], [17, 34], [16, 30]], 2.0, 1.5, { grain: 3, ph: 1.1 });  });}function crossbar(g) {  g.part("crossbar", () => {    limb(g, [[2, 30], [7, 29], [14, 29], [20, 28], [25, 29], [29, 27]], 1.6, 1.1, { ph: 0.9, grain: 4 });    limb(g, [[4, 29], [2, 26], [2, 24]], 0.8, 0.4, { ph: 2.0 });          // a twig left on the end    limb(g, [[26, 28], [28, 25]], 0.7, 0.4, { ph: 0.4 });    g.pixel(22, 28, K("w0")); g.pixel(23, 28, K("w1"));                     // a knot  });}function dish(g) {  g.part("bracket", () => {    g.line([17, 35], [21, 35], K("n2"));    g.line([17, 36], [20, 36], K("n0"));    g.pixel(17, 35, K("n3"));  });  g.part("dish", () => {    // a shallow brass bowl: lit rim, dark inside, rounded belly    g.ellipse({ x: [20, 28], y: [32, 35] }, K("g1"), { fill: K("g1") });    g.line([21, 36], [27, 36], K("g1"));    g.line([22, 37], [26, 37], K("g0"));    g.line([20, 34], [22, 36], K("g2"));    g.line([27, 35], [26, 36], K("g0"));    g.ellipse({ x: [21, 27], y: [32, 34] }, K("g0"), { fill: K("g0") });    g.line([21, 32], [24, 32], K("g3")); g.pixel(20, 33, K("g2"));          // the lit rim edge    g.line([25, 32], [27, 32], K("g2")); g.pixel(28, 33, K("g1"));    g.pixel(23, 33, K("w0")); g.pixel(24, 33, K("n1")); g.pixel(25, 34, K("w0"));   // something in it  });  g.part("mouseTail", () => {    g.pixel(26, 33, K("n3")); g.pixel(27, 33, K("n3")); g.pixel(28, 34, K("n3"));    g.pixel(29, 35, K("n3")); g.pixel(29, 36, K("n2")); g.pixel(28, 37, K("n2"));  });}// ---- the owl -------------------------------------------------------------------------------------------------// pose: { hy head offset, chest 0/1 breath, blink 0..1, turn 0..1, puff 0..1, beak bool, ruffle 0..1 }function tail(g) {  g.part("tail", () => {    g.fillPoly([[12, 27], [19, 27], [18, 32], [13, 32]], K("w1"));    g.line([13, 28], [13, 31], K("w2"));    g.line([14, 30], [17, 30], K("w0"));    g.line([13, 32], [17, 32], K("w2"));  });}// breast streaks: short dark shaft marks, placed by hand so they read as feathers, not stripesconst STREAKS = [[-2, -2], [1, -1], [-1, 2], [2, 3], [0, 5], [-2, 6]];function body(g, p) {  // a pear: narrow under the head, full at the belly; it swells when the owl breathes or puffs up  const cx = OX + 0.5, rx = 6.6 + p.puff * 1.3 + p.ruffle * 0.4, cy = 20.5 - p.chest * 0.5, ry = 7 + p.chest * 0.5 + p.puff * 0.3;  const streak = (x, y) => STREAKS.some(([dx, dy]) => x === OX + dx && (y === Math.round(cy) + dy || y === Math.round(cy) + dy + 1));  g.part("body", () => g.field({ x: [OX - 10, OX + 11], y: [11, 28] }, (x, y) => {    const ny = (y + 0.5 - cy) / ry;    const nx = (x + 0.5 - cx) / (rx * (0.86 + 0.14 * Math.min(1, ny + 0.4)));    let r2 = nx * nx + ny * ny;    // ruffled: the outline breaks into feather tips    if (p.ruffle > 0 && r2 > 0.85 && r2 < 1.3 && Math.abs(nx) > 0.5 && (y + (x > OX ? 1 : 0)) % 2 === 0) r2 = 0.9;    if (r2 > 1 || y > 27) return null;    const i = litIndex(x, y, cx, cy, rx, ry, 4, 0.05);    if (y <= 15 + p.hy) return K(i >= 2 ? "w1" : "w0");          // shadow under the head    if (p.puff > 0.4 && ny < -0.2 && Math.abs(nx) < 0.35) return K("n5");   // white throat puffed out    if (streak(x, y)) return K(i >= 3 ? "w2" : "w1");    return K(["w1", "w2", "w3", "w4"][i]);  }));}// folded wings lie over the flanks: a tapering shape from the shoulder to a tip that crosses the tailfunction wings(g, p) {  const out = Math.round(p.puff * 1.2 + p.ruffle * 0.6);  for (const s of [-1, 1]) {    const lit = s < 0;    const path = [[OX + 0.5 + s * (5.4 + out), 15], [OX + 0.5 + s * (6.3 + out), 20], [OX + 0.5 + s * (5.3 + out), 25], [OX + 0.5 + s * 2.4, 29]];    g.part(lit ? "wingL" : "wingR", () => g.sweep(path, { w0: 2.3, w1: 0.7, wobble: p.ruffle * 0.25, wobblePhase: s }, (u, v) => {      const front = v * -s;                                         // +1 at the edge facing the breast      if (front > 0.55 && u < 0.75) return K(lit ? "w0" : "w0");    // the wing's front edge      if (front > -0.1 && front < 0.5 && u > 0.1 && u < 0.5 && Math.abs(((u * 7.5) % 1) - 0.5) < 0.13) return K("n4");   // pale scapular spots      const bar = (u * 9 + v * 0.6) % 1 < 0.3;      const l = (lit ? 0.1 : -0.45) - front * 0.2 + (bar ? -0.6 : 0);      return K(l > 0.3 ? "w3" : l > -0.25 ? "w2" : l > -0.75 ? "w1" : "w0");    }));  }}function head(g, p) {  const cx = OX + 0.5, cy = 9.5 + p.hy, rx = 8.1 + p.puff * 0.3, ry = 6.1;  const fx = Math.round(p.turn * 3);                 // the face swings this far to the right  const fcx = cx + fx, fcy = 10 + p.hy;  const E = [[OX - 4 + fx, 8 + p.hy], [OX + 3 + fx, 8 + p.hy]];  // eye top-left corners (3x3)  const inHead = (x, y) => { const nx = (x + 0.5 - cx) / (rx - 0.7), ny = (y + 0.5 - cy) / (ry - 0.7); return nx * nx + ny * ny <= 1; };  g.part("head", () => g.field({ x: [OX - 9, OX + 10], y: [2 + p.hy, 17 + p.hy] }, (x, y) => {    const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry;    if (nx * nx + ny * ny > 1) return null;    const i = litIndex(x, y, cx, cy, rx, ry, 4, 0.1);    // facial disc: one grey disc, notched at the top by the brows, rimmed in dark brown    const dx = (x + 0.5 - fcx) / 5.6, dy = (y + 0.5 - fcy) / 4.4;    const dr = Math.sqrt(dx * dx + dy * dy) + (dy < -0.4 && Math.abs(dx) < 0.25 ? 0.25 : 0);    if (dr < 1.2 && inHead(x, y)) {      if (dr < 1) {        const d0 = Math.min(Math.hypot(x - E[0][0] - 1, y - E[0][1] - 1), Math.hypot(x - E[1][0] - 1, y - E[1][1] - 1));        return K(d0 < 2.6 ? (i >= 2 ? "n4" : "n3") : (i >= 2 ? "n3" : "n2"));      }      if (dy > -0.5) return K("w0");    }    // crown: tawny, flecked    if ((x * 3 + y * 2) % 7 === 0 && ny < 0.4) return K(i >= 2 ? "w1" : "w0");    if ((x * 3 + y * 2) % 7 === 3 && ny < 0 && i >= 2) return K("w4");    return K(["w0", "w1", "w2", "w3"][i]);  }));  // pale brows over the eyes meeting above the beak (the stern look of a witch owl)  g.part("brows", () => {    const by = 6 + p.hy;    for (const [px, py] of [[OX + fx, by + 1], [OX - 1 + fx, by], [OX - 2 + fx, by], [OX - 3 + fx, by],      [OX + 1 + fx, by + 1], [OX + 2 + fx, by], [OX + 3 + fx, by], [OX + 4 + fx, by]])      if (inHead(px, py)) g.pixel(px, py, K(px <= OX + fx ? "n5" : "n4"));  });  g.part("eyes", () => {    E.forEach(([ex, ey], k) => {      for (let dy = 0; dy < 3; dy++) for (let dx = 0; dx < 3; dx++) {        const x = ex + dx, y = ey + dy;        if (!inHead(x, y)) continue;        if (k === 1 && p.turn > 0.6 && dx === 2) continue;        // the far eye foreshortens as the head turns        const lid = p.blink >= 1 ? 3 : p.blink > 0 ? 1 : 0;        let c;        if (dy < lid) c = dy === 2 || (lid === 1 && dy === 0) ? "w0" : "w2";        else if (p.night) c = dx === 1 && dy >= 1 ? "g3" : dy === 0 ? "e2" : "e3";        else if (dx === 1 && dy >= 1) c = "ink";        else if (dx === 0 && dy === lid) c = "g3";        else c = dy === 2 ? "g1" : "g2";        g.pixel(x, y, p.night && dy >= lid ? c : K(c));      }    });  });  g.part("beak", () => {    const bx = OX + fx, by = 10 + p.hy;    if (p.beak) {      g.pixel(bx, by, K("n4")); g.pixel(bx + 1, by, K("n3"));      g.pixel(bx, by + 1, "ink"); g.pixel(bx + 1, by + 1, "r0");      g.pixel(bx, by + 2, K("n3")); g.pixel(bx + 1, by + 2, K("n2"));    } else {      g.pixel(bx, by, K("n4")); g.pixel(bx + 1, by, K("n3"));      g.pixel(bx, by + 1, K("n3")); g.pixel(bx + 1, by + 1, K("n2"));      g.pixel(bx, by + 2, K("n2"));    }  });}function feet(g) {  g.part("talons", () => {    for (const x of [OX - 3, OX + 2]) {      g.pixel(x, 27, K("n4")); g.pixel(x + 1, 27, K("n3")); g.pixel(x + 2, 27, K("n3"));      g.pixel(x, 28, K("n1")); g.pixel(x + 2, 28, K("n1"));    }  });}function sound(g, f) {  // two concentric arcs ripple out from the beak to the right  g.detached("sound", () => {    for (const k of [0, 1]) {      const age = f - 2 - k;      if (age < 0 || age > 2) continue;      const r = 2 + age + k * 2.5;      const key = age === 0 ? "n5" : age === 1 ? "n4" : "n3";      for (let a = -0.9; a <= 0.9; a += 0.1) {        const x = Math.round(OX + 7 + r * Math.cos(a)), y = Math.round(10 + r * Math.sin(a) * 1.25);        if (x < W) g.pixel(x, y, key);      }    }  });}function idlePose(t) {  const i = Math.round(t * 16) % 16;  const s = Math.sin(TAU * t);  return {    hy: s > 0.35 ? -1 : 0, chest: s > 0.35 ? 1 : 0,    blink: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 1, 0.5, 0, 0, 0, 0][i],    ruffle: [0, 0, 0, 0.5, 1, 1, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0][i],    turn: 0, puff: 0, beak: false,  };}const OwlPerch = sprite(pal, (g, t, mode) => {  t = ((t % 1) + 1) % 1;  NIGHT_ON = mode === "night";  let p = idlePose(mode === "look" || mode === "hoot" ? 0 : t);  let f = 0;  if (mode === "look") {    f = Math.min(9, Math.round(t * 10));    p = { ...p, turn: [0, 0.35, 0.7, 1, 1, 1, 1, 0.65, 0.3, 0][f], blink: f === 5 ? 1 : f === 4 || f === 6 ? 0.5 : 0 };  }  if (mode === "hoot") {    f = Math.min(7, Math.round(t * 8));    const puff = [0, 0.5, 1, 1, 1, 0.6, 0.2, 0][f];    p = { ...p, puff, beak: f >= 2 && f <= 4, hy: f >= 2 && f <= 4 ? 1 : 0, chest: f === 1 || f === 5 ? 1 : 0 };  }  if (mode === "night") {    const i = Math.round(t * 16) % 16;    p = { ...p, blink: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.5, 1, 0.5, 0][i], ruffle: 0 };  }  p.night = NIGHT_ON;  contactShadow(g, 16, GROUND, 11, 1);  g.outlined("ink", () => {    perch(g);    dish(g);    tail(g);    crossbar(g);    body(g, p);    wings(g, p);    head(g, p);    feet(g);  });  if (mode === "hoot") sound(g, f);});submit(OwlPerch, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: 16, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "look", args: ["look"], animate: { fps: 10, duration: 1.0, loop: false } },    { name: "hoot", args: ["hoot"], animate: { fps: 10, duration: 0.8, loop: false } },    { name: "night", args: ["night"] },  ],});

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.