Raven tree
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.
6×
Get 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 — 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 fallsThe last lines. submit declares the canvas and lists the variants the renderer exports.
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 — 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.