Tessarune
Graveyard & Chapel · raven_tree.js

Raven tree

64×64 · 269 lines of JavaScript

A 64×64 floor prop: a gnarled, leafless dead tree with a twisted trunk, spiral grooves, a hollow knot, exposed roots and crooked branches, a few dead leaves on the ground, and a black raven perched on the right bough. “idle” (16 frames) creaks the branches while the raven shifts and cocks its head and one dead leaf spins down; “caw” (10 frames, once) has the raven hunch, gape and half-spread its wings before it settles; “empty” (16 frames) shows the tree alone, a lost feather on the ground, with a leaf falling.

Raven tree — Rendered output6×
Rendered by this program, variant “caw” · 64×64 · 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.

raven_tree.js · 1–7
// Raven tree — 64x64 floor prop, top-down 3/4, key light top-left.// A gnarled, leafless dead tree: a twisted trunk with spiral grooves and a hollow knot, exposed roots, crooked// branches, a few dead leaves on the ground. A black raven perches on the right bough.// Variants://   idle   16f loop  branches creak, the raven shifts and cocks its head, one dead leaf spins down//   caw    10f once  the raven hunches, gapes, half-spreads its wings and settles; ends on idle frame 0//   empty  16f loop  the tree alone (the raven flew off; a lost feather lies on the ground), leaf falls

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

raven_tree.js · 259–269
submit(RavenTree, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle" },    { name: "caw", args: ["caw"], animate: { fps: 10, duration: 1.0, loop: false } },    { name: "empty", args: ["empty"] },  ],});
Show the whole program (269 lines)
raven_tree.js · 269 lines
// Raven tree — 64x64 floor prop, top-down 3/4, key light top-left.// A gnarled, leafless dead tree: a twisted trunk with spiral grooves and a hollow knot, exposed roots, crooked// branches, a few dead leaves on the ground. A black raven perches on the right bough.// Variants://   idle   16f loop  branches creak, the raven shifts and cocks its head, one dead leaf spins down//   caw    10f once  the raven hunches, gapes, half-spreads its wings and settles; ends on idle frame 0//   empty  16f loop  the tree alone (the raven flew off; a lost feather lies on the ground), leaf fallsconst W = 64, H = 64, CX = 32, GROUND = 61;const L = [-0.74, -0.67];                                   // key light direction (top-left)const BARK = ["n0", "w0", "w1", "w2", "n3"];                // cool shadow -> lit bark// ---- tree -------------------------------------------------------------------------------------------------// a limb swept along a path; shaded as a cylinder lit from the top-left (v runs across the limb)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];         // how much the +v side faces the light  const grooves = opt.grooves || 0;  g.sweep(path, { w0, w1, wobble: opt.wobble ?? 0.15, wobblePhase: opt.ph ?? 0.7 }, (u, v) => {    let l = v * nl * 1.3 + 0.12 - (opt.dark || 0);    if (grooves) {                                           // twisted trunk: spiral grooves, one step darker      const s = u * grooves + v * 0.55;      if (Math.abs(v) < 0.7 && s - Math.floor(s) < 0.2) l -= 0.55;    }    return BARK[l > 0.9 ? 4 : l > 0.12 ? 3 : l > -0.38 ? 2 : l > -0.8 ? 1 : 0];  });}// creak: tips drift by up to ~1px, periodic in t (a whole cycle per loop)const creak = (t, [x, y], k, ph) => [x + 0.9 * k * Math.sin(TAU * (t + ph)), y + 0.35 * k * Math.sin(TAU * (t + ph + 0.25))];const PERCH = { x: 46, y: 26 };                             // the raven's feetfunction tree(g, t) {  g.part("roots", () => {    limb(g, [[29, 56], [25, 59], [21, 60], [16, 61]], 2.4, 0.5, { ph: 0.2 });    limb(g, [[35, 56], [39, 59], [43, 60], [48, 61]], 2.4, 0.5, { ph: 1.4, dark: 0.1 });    limb(g, [[33, 57], [34, 60], [36, 61]], 1.8, 0.7, { ph: 2.1 });    limb(g, [[30, 57], [28, 60], [27, 61]], 1.6, 0.6, { ph: 0.5 });  });  g.part("trunk", () => {    limb(g, [[32, 58], [33, 51], [30, 43], [32, 35], [30, 28], [31, 22]], 4.3, 2.0, { grooves: 3.5, wobble: 0.14, ph: 1.9 });  });  g.part("branches", () => {    // left bough, gnarled, with a drooping fork and an upturned twig    limb(g, [[30, 36], [26, 33], [22, 32], [18, 28], [14, 27], creak(t, [9, 22], 1, 0.1)], 2.0, 0.4, { ph: 0.3 });    limb(g, [[18, 29], [13, 32], creak(t, [8, 33], 1, 0.35)], 1.0, 0.4, { ph: 2.4 });    limb(g, [[22, 32], [20, 27], creak(t, [21, 23], 0.7, 0.6)], 0.9, 0.4, { ph: 1.1 });    // top leader, crooked, with side twigs    limb(g, [[31, 24], [33, 17], [29, 12], [31, 7], creak(t, [28, 2], 1, 0.5)], 2.0, 0.4, { ph: 0.9 });    limb(g, [[32, 17], [35, 15], creak(t, [36, 12], 0.6, 0.8)], 0.9, 0.4, { ph: 1.7 });    limb(g, [[30, 11], [26, 9], creak(t, [24, 5], 0.7, 0.2)], 0.8, 0.4, { ph: 0.4 });    // upper-left arm    limb(g, [[30, 25], [26, 21], [24, 16], [19, 13], creak(t, [17, 9], 0.9, 0.9)], 1.4, 0.4, { ph: 2.8 });    // right bough: the raven's perch (stiff where the bird sits), tip creaks    limb(g, [[32, 34], [38, 30], [44, 28], [50, 26], [55, 24], creak(t, [60, 20], 1, 0.3)], 2.0, 0.4, { ph: 1.2, dark: 0.05 });    limb(g, [[50, 26], [54, 29], creak(t, [58, 31], 0.8, 0.7)], 0.9, 0.4, { ph: 0.6 });    limb(g, [[38, 30], [41, 33], creak(t, [40, 37], 0.6, 0.4)], 0.8, 0.4, { ph: 2.2 });  });}function barkMarks(g) {  g.part("knot", () => {    // a hollow knot on the trunk: dark hole, lit lower lip, shaded upper rim    g.ellipse({ x: [30, 33], y: [42, 46] }, "ink", { fill: "ink" });    g.pixel(31, 43, "v0");    g.line([30, 47], [32, 47], "w2"); g.pixel(29, 46, "n3");    g.line([30, 41], [32, 41], "w0");  });  g.part("barkMarks", () => {    g.pixel(29, 52, "w3"); g.pixel(28, 55, "w3");          // lit bark ridges near the flare    g.pixel(35, 38, "w0"); g.pixel(35, 53, "w0");  });}// ---- ground litter ----------------------------------------------------------------------------------------function litter(g, feather) {  g.part("litter", () => {    for (const [x, y, k] of [[13, 60, "w3"], [15, 61, "w2"], [21, 62, "r1"], [49, 61, "w3"], [52, 60, "w2"], [41, 62, "w2"]]) {      g.pixel(x, y, k); g.pixel(x + 1, y, k === "w3" ? "w2" : "w1");    }    g.pixel(24, 61, "n2"); g.pixel(24, 60, "n3"); g.pixel(25, 59, "n2");       // dry grass tufts    g.pixel(45, 60, "n2"); g.pixel(46, 59, "n3"); g.pixel(40, 60, "n2");    if (feather) {                                                             // a lost raven feather      g.line([51, 62], [55, 60], "v0"); g.pixel(52, 61, "b0"); g.pixel(56, 60, "n3");    }  });}// ---- falling leaf -----------------------------------------------------------------------------------------function leaf(g, t) {  const p = ((t % 1) + 1) % 1;  const fall = 0.82;  if (p > fall + 0.1) return;                                // it has landed in the litter (13,60)  const q = Math.min(1, p / fall);  const x = Math.round(13 + 4 * Math.sin(TAU * q * 2.2) * Math.sin(Math.PI * q) - q * 0.5);  const y = Math.round(21 + q * q * 0.35 * 39 + q * 0.65 * 39);  const spin = Math.floor(p * 32) % 4;  g.detached("leaf", () => {    if (q >= 1) { g.pixel(13, 60, "w3"); g.pixel(14, 60, "w2"); return; }    if (spin === 0) { g.pixel(x, y, "w3"); g.pixel(x + 1, y, "r1"); }    else if (spin === 1) { g.pixel(x, y, "w3"); g.pixel(x + 1, y + 1, "w1"); }    else if (spin === 2) { g.pixel(x, y, "w2"); g.pixel(x, y + 1, "w1"); }    else { g.pixel(x, y, "w1"); g.pixel(x + 1, y, "w2"); }  });}// ---- raven ------------------------------------------------------------------------------------------------// Drawn in bird-local coords (x < 0 = forward, toward the head) and mirrored into the world so it faces right,// out over open sky. Lighting is computed in world space, so the top-left of each mass catches the key light.// pose: { by body drop, hx/hy head offset, look "fwd"|"down"|"up"|"back", gape 0..2, wing 0..1, tail }const WING_TUCK = [[-1, -7], [1, -8], [5, -8], [7, -6], [6, -5], [7, -4], [5, -4], [5, -3], [3, -3]];const WING_OPEN = [[-1, -7], [-1, -12], [3, -14], [7, -12], [7, -10], [8, -8], [7, -6], [6, -4], [3, -3]];// primary feathers: [base (tucked position), spread tip]const FINGERS = [[[2, -9], [-1, -18]], [[3, -8], [3, -19]], [[4, -7], [7, -18]], [[5, -6], [11, -15]]];function raven(g, P) {  const X = PERCH.x, Y = PERCH.y;  const at = (x, y) => [X - Math.round(x), Y + Math.round(y)];  const by = P.by || 0, s = P.wing || 0;  g.part("ravenLegs", () => {    g.line(at(-1, -2), at(-1, 0), "n2"); g.line(at(1, -2), at(1, 0), "n1");    g.pixel(...at(-2, 0), "n3"); g.pixel(...at(2, 0), "n2");  });  if (s > 0) {                                               // far wing: its tip shows above the near wing    g.part("ravenWingFar", () => {      const pts = [[-2, -8], [-2, -8 - 5 * s], [3, -9 - 7 * s], [3, -8]].map(([x, y]) => at(x, y + by));      g.fillPoly(pts, "ink");      g.line(pts[1], pts[2], "v0");    });  }  g.part("ravenBody", () => {    g.field({ x: [X - 10, X + 6], y: [Y - 10 + by, Y + 3 + by] }, (x, y) => {      const lx = X - x, ly = y - Y - by;      const dx = lx - 0.5, dy = ly + 5.2;      const pu = (dx * -0.8 + dy * -0.6) / 5.4, pv = (dx * 0.6 + dy * -0.8) / 3.4;   // body leans chest-up      const inBody = pu * pu + pv * pv <= 1.02;      const nx = dx / 4.6, ny = dy / 4.2;      // tail: a wedge hanging down and back behind the perch      const ax = 3, ay = -3.5, bx = 8 + (P.tail || 0), bY = 1.5;      const tx = bx - ax, ty = bY - ay, tl2 = tx * tx + ty * ty;      const k = ((lx - ax) * tx + (ly - ay) * ty) / tl2;      const dperp = Math.abs((lx - ax) * ty - (ly - ay) * tx) / Math.sqrt(tl2);      const inTail = k >= 0 && k <= 1 && dperp < 1.9 - 0.5 * k;      if (!inBody && !inTail) return null;      if (!inBody) {        const side = (lx - ax) * ty - (ly - ay) * tx;          // > 0 = upper (lit) side of the tail        return side > 0.4 ? "v0" : "ink";      }      const d = 0.6 * nx - 0.8 * ny;                           // world light: top-left (local +x is world left)      if (d > 0.72) return "b0";      if (d > 0.0) return "v0";      return "ink";    });  });  g.part("ravenWing", () => {    const L2 = (A, B) => at(A[0] + (B[0] - A[0]) * s, A[1] + (B[1] - A[1]) * s + by);    const pts = WING_TUCK.map((p, i) => L2(p, WING_OPEN[i]));    g.fillPoly(pts, "v0");    g.line(pts[0], pts[1], "b0");                               // lit leading edge (arm)    if (s > 0.25) {      // primaries fan out as separate fingers past the wing's edge      FINGERS.forEach(([b0, tip], i) => {        const base = at(b0[0], b0[1] + by), end = L2(b0, tip);        g.line(base, end, "v0");        g.pixel(...end, i === 0 ? "b1" : "b0");      });      g.line(pts[1], pts[2], "b0");      g.pixel(...pts[1], "b1");    } else {      g.line(at(2, -4 + by), at(6, -4 + by), "ink");           // folded primaries edge      g.pixel(...at(4, -6 + by), "ink");      g.line(pts[1], pts[2], "v0");    }  });  const hx = -4 + (P.hx || 0), hy = -10 + by + (P.hy || 0);  g.part("ravenHead", () => {    const [cx, cy] = at(hx, hy);    g.ellipse({ x: [cx - 2, cx + 2], y: [cy - 2, cy + 1] }, "v0", { fill: "v0" });    g.line([cx - 1, cy - 2], [cx, cy - 2], "b0");    g.pixel(cx + 2, cy + 1, "ink"); g.pixel(cx + 1, cy + 1, "ink"); g.pixel(cx + 2, cy, "ink");    g.pixel(cx, cy + 2, "ink"); g.pixel(cx + 1, cy + 2, "ink");              // throat hackles  });  g.part("ravenBeak", () => {    const look = P.look || "fwd", o = P.gape || 0;    const B = (x, y) => at(hx + x, hy + y);    if (look === "back") {      g.fillPoly([B(1, -1), B(5, -1), B(1, 0)], "n1"); g.line(B(1, -1), B(4, -1), "n2");    } else if (o > 0) {      g.fillPoly([B(-2, -1), B(-6, -1 - o), B(-2, 0)], "n1");          // upper mandible      g.line(B(-2, -1), B(-5, -1 - o), "n2");      g.fillPoly([B(-2, 1), B(-5, 1 + o), B(-2, 1)], "n0");            // lower mandible      g.line(B(-2, 1), B(-5, 1 + o), "n1");      g.pixel(...B(-3, 0), "r0"); if (o > 1) { g.pixel(...B(-3, 1), "r1"); g.pixel(...B(-4, 0), "r0"); g.pixel(...B(-4, 1), "r0"); }    } else {      const dy = look === "down" ? 1 : look === "up" ? -1 : 0;      g.fillPoly([B(-2, -1), B(-6, dy), B(-2, 1)], "n1");      g.line(B(-2, -1), B(-5, dy), "n2");                              // lit ridge (culmen)    }  });  g.part("ravenEye", () => g.pixel(...at(P.look === "back" ? hx + 1 : hx - 1, hy - 1), "g3"));  g.part("ravenSheen", () => g.line(at(1, -8 + by), at(3, -8 + by), "b1"));   // one lit sheen on the back}// idle choreography over 16 frames: hold, cock head down, hold, look back, hold, shift weightfunction idlePose(t) {  const f = Math.round(((t % 1) + 1) % 1 * 16) % 16;  if (f >= 3 && f <= 5) return { look: "down", hx: -1, hy: 1 };  if (f === 6) return { look: "up", hy: -1 };  if (f >= 9 && f <= 11) return { look: "back", hx: 1 };  if (f === 13) return { by: 1, tail: 1 };  if (f === 14) return { by: 0, tail: 1, hy: -1 };  return {};}// caw over 10 frames; frame 9 is idle frame 0const CAW = [  {},  { by: 1, hx: -1, hy: 1, look: "down" },  { by: 1, hx: -1, hy: 1, gape: 1, wing: 0.4 },  { by: 0, hx: -1, hy: -1, gape: 2, wing: 0.85, tail: 1 },  { by: 0, hx: -1, hy: -1, gape: 2, wing: 1, tail: 1, sound: 1 },  { by: 0, hx: -1, hy: -1, gape: 2, wing: 0.9, tail: 1, sound: 2 },  { by: 0, hx: -1, gape: 1, wing: 0.5 },  { by: 1, hx: -1, hy: 1, wing: 0.2, look: "down" },  { by: 1 },  {},];function sound(g, s) {  const X = PERCH.x + 13, Y = PERCH.y - 13;  g.detached("caw", () => {    // two sound arcs opening away from the beak; the first one travels out and fades    const o = s === 1 ? 0 : 3, k = s === 1 ? "n4" : "n3";    g.pixel(X + o, Y - 2, k); g.pixel(X + o + 1, Y - 1, k); g.pixel(X + o + 1, Y, k); g.pixel(X + o, Y + 1, k);    if (s === 2) { g.pixel(X, Y - 1, "n4"); g.pixel(X + 1, Y, "n4"); g.pixel(X, Y + 1, "n4"); }  });}const RavenTree = sprite(pal, (g, t, state) => {  t = ((t % 1) + 1) % 1;  let ft = t, P = null;  if (state === "idle") P = idlePose(t);  else if (state === "caw") {    const f = Math.min(9, Math.round(t * 10));    ft = ((f - 9) / 16 + 1) % 1;                             // tree and leaf on the idle clock; f9 = idle t 0    P = CAW[f];  }  contactShadow(g, CX, GROUND, 13, 2);  litter(g, state === "empty");  g.outlined("ink", () => {    tree(g, ft);    if (P) raven(g, P);  });  barkMarks(g);  leaf(g, ft);  if (P && P.sound) sound(g, P.sound);});submit(RavenTree, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle" },    { name: "caw", args: ["caw"], animate: { fps: 10, duration: 1.0, loop: false } },    { name: "empty", args: ["empty"] },  ],});

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.