Autumn tree
A 64×64 floor prop of an autumn tree with a furrowed trunk, a crown of clustered orange, red, and gold leaves, and fallen leaves around the roots. In the 16-frame idle loop, the crown sways as three leaves spin down into the ring. The gust variant is a 12-frame loop where wind shudders the crown, while the 16-frame apples variant drops a red apple that rolls into the grass.
6×
Get the animated sprites: Harvest Festival on itch.ioVol.6 · 12 harvest-fair props · 37 animations
The header. The comment at the top of the file is the brief: size, view, light direction and every variant.
// Autumn tree — 64x64 floor prop (large), top-down 3/4, key light top-left.// A full, round-crowned autumn tree: a sturdy trunk with furrowed bark and a root flare, three limbs forking into a// crown built from clustered leaf masses (orange, red and gold, with a dry brown clump and a last green one on the shaded side). Every// clump is lit from the top-left on its own; clumps behind are darkened where the clumps in front overlap them, and// the whole crown darkens toward its underside. A ring of fallen leaves lies in the grass around the roots.// Variants:// idle 16f loop the crown sways a little (top more than bottom), three leaves fall, spin and land in the ring// gust 12f loop strong wind from the left: the crown leans right and shudders, a flurry of leaves blows across// apples 16f loop apple-tree version (green-gold crown, red apples); one apple drops, bounces, rolls into the// grass tuft, and a new one lowers into view from under the leaves so the loop closesThe last lines. submit declares the canvas and lists the variants the renderer exports.
submit(AutumnTree, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, anchor: { x: CX, y: GROUND }, variants: [ { name: "idle", args: ["idle"], animate: { fps: 10, duration: N_IDLE / 10, loop: true } }, { name: "gust", args: ["gust"], animate: { fps: 10, duration: N_GUST / 10, loop: true } }, { name: "apples", args: ["apples"], animate: { fps: 10, duration: N_APPLE / 10, loop: true } }, ],});Show the whole program (280 lines)
// Autumn tree — 64x64 floor prop (large), top-down 3/4, key light top-left.// A full, round-crowned autumn tree: a sturdy trunk with furrowed bark and a root flare, three limbs forking into a// crown built from clustered leaf masses (orange, red and gold, with a dry brown clump and a last green one on the shaded side). Every// clump is lit from the top-left on its own; clumps behind are darkened where the clumps in front overlap them, and// the whole crown darkens toward its underside. A ring of fallen leaves lies in the grass around the roots.// Variants:// idle 16f loop the crown sways a little (top more than bottom), three leaves fall, spin and land in the ring// gust 12f loop strong wind from the left: the crown leans right and shudders, a flurry of leaves blows across// apples 16f loop apple-tree version (green-gold crown, red apples); one apple drops, bounces, rolls into the// grass tuft, and a new one lowers into view from under the leaves so the loop closesconst W = 64, H = 64, CX = 32, GROUND = 61;const N_IDLE = 16, N_GUST = 12, N_APPLE = 16;const L = [-0.74, -0.67]; // key light direction (top-left)// leaf ramps: index 0 = occluded shadow, 4 = lit edgeconst RAMP = { r: ["w0", "r0", "r1", "r2", "r3"], // red o: ["r0", "r1", "r2", "r3", "g2"], // orange y: ["w1", "g0", "g1", "g2", "g3"], // gold w: ["w0", "w1", "w2", "w3", "w4"], // dry brown e: ["e0", "e1", "e2", "e3", "g2"], // green d: ["e0", "e0", "e1", "e2", "e3"], // deep green (apple tree) a: ["e0", "e0", "e1", "e2", "g2"], // apple-tree green};// clumps back -> front: [cx, cy, r, autumn ramp, apple ramp]const CLUMPS = [ [27, 10, 7.5, "o", "a"], [39, 11, 7.0, "y", "y"], [18, 16, 6.8, "r", "d"], [47, 17, 6.6, "d", "a"], [32, 15, 7.6, "y", "a"], [11, 25, 6.0, "o", "a"], [53, 25, 6.0, "w", "d"], [22, 22, 7.8, "r", "y"], [42, 22, 7.6, "o", "d"], [33, 26, 4.8, "w", "a"], [16, 31, 6.6, "y", "a"], [48, 31, 6.4, "r", "y"], [29, 31, 7.6, "o", "d"], [40, 34, 6.0, "y", "a"], [22, 36, 4.8, "r", "d"],];const CROWN = { cx: 32, cy: 22, rx: 24, ry: 19 };// clump membership with a scalloped (leafy) rimfunction inClump(c, ox, x, y) { const [cx, cy, r] = c; const dx = x + 0.5 - (cx + ox[0]), dy = y + 0.5 - (cy + ox[1]); const ry = r * 0.9; const d = Math.sqrt((dx * dx) / (r * r) + (dy * dy) / (ry * ry)); const a = Math.atan2(dy, dx); return d < 1 + 0.13 * Math.sin(a * 7 + cx * 1.3) + 0.06 * Math.sin(a * 11 + cy);}// countable leaf marks per clump (relative to the clump centre): lit leaf tips upper-left, one dark notch lower-rightfunction mark(c, rx, ry) { const r = c[2]; const a = Math.round(-0.45 * r), b = Math.round(-0.35 * r); if ((rx === a && ry === b) || (rx === a + 1 && ry === b) || (rx === a && ry === b + 1)) return 2; const p = Math.round(0.1 * r), q = Math.round(-0.6 * r); if ((rx === p && ry === q) || (rx === p + 1 && ry === q - 1)) return 2; const s = Math.round(-0.75 * r), u = Math.round(0.05 * r); if (rx === s && ry === u) return 1; const m = Math.round(0.35 * r), n = Math.round(0.4 * r); if ((rx === m && ry === n) || (rx === m + 1 && ry === n)) return -1; return 0;}function crown(g, offs, rampIdx) { const n = CLUMPS.length; g.part("crown", () => g.field({ x: [0, W - 1], y: [0, 46] }, (x, y) => { let top = -1; for (let i = n - 1; i >= 0; i--) if (inClump(CLUMPS[i], offs[i], x, y)) { top = i; break; } if (top < 0) return null; const c = CLUMPS[top], o = offs[top]; const cx = c[0] + o[0], cy = c[1] + o[1], r = c[2]; // each clump: a lit crescent on its upper-left, a body tone, a shaded lower-right const nx = (x + 0.5 - cx) / (r * 1.05), ny = (y + 0.5 - cy) / (r * 0.95); const nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny)); const d = -0.62 * nx - 0.62 * ny + 0.3 * nz - 0.05; // whole-crown light: the underside and the far right sit in shade const gx = (x + 0.5 - CROWN.cx) / CROWN.rx, gy = (y + 0.5 - CROWN.cy) / CROWN.ry; const G = -0.45 * gx - 0.6 * gy; const li = d > 0.6 && G > -0.15 ? 3 : d > 0.12 ? 2 : d > -0.35 ? 1 : 0; let idx = 1 + li; if (G < -0.2) idx -= 1; if (G < -0.7) idx -= 1; // contact shade on a back clump around the clumps in front of it (+ their cast shadow to the bottom-right) for (let j = top + 1; j < n; j++) { const D = CLUMPS[j], q = offs[j]; if (inClump(D, q, x, y + 1.6) || inClump(D, q, x + 1.4, y + 0.6) || inClump(D, q, x - 1.4, y + 0.6) || inClump(D, q, x - 2, y - 2)) { idx -= 1; break; } } const mk = mark(c, x - Math.round(cx), y - Math.round(cy)); if (mk > 0 && li >= 1) idx = Math.max(idx, Math.min(4, li + mk)); if (mk < 0) idx -= 1; const ramp = RAMP[c[rampIdx]]; return ramp[Math.max(0, Math.min(4, idx))]; }));}// ---- trunk & limbs ----------------------------------------------------------------------------------------const BARK = ["n0", "w0", "w1", "w2", "w3"];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.12, wobblePhase: opt.ph ?? 0.7 }, (u, v) => { let l = v * nl * 1.25 + 0.15 - (opt.dark || 0); if (opt.furrows) { // vertical bark furrows, one step darker const s = v * 2.3 + u * 0.4 + 0.2; if (Math.abs(v) < 0.8 && s - Math.floor(s) < 0.2) l -= 0.5; } return BARK[l > 0.95 ? 4 : l > 0.2 ? 3 : l > -0.35 ? 2 : l > -0.8 ? 1 : 0]; });}function tree(g) { g.part("roots", () => { limb(g, [[29, 56], [26, 59], [22, 60], [19, 61]], 2.6, 0.6, { ph: 0.2 }); limb(g, [[35, 56], [38, 59], [42, 60], [45, 61]], 2.6, 0.6, { ph: 1.4, dark: 0.1 }); limb(g, [[31, 58], [30, 61]], 2.0, 1.0, { ph: 2.1 }); limb(g, [[34, 58], [36, 61]], 1.8, 0.9, { ph: 0.5, dark: 0.1 }); }); g.part("limbs", () => { limb(g, [[31, 43], [27, 37], [22, 32], [18, 28]], 2.4, 1.0, { ph: 0.3 }); limb(g, [[33, 43], [37, 37], [42, 32], [46, 28]], 2.4, 1.0, { ph: 1.1, dark: 0.05 }); limb(g, [[32, 42], [33, 35], [31, 28]], 2.2, 1.0, { ph: 2.4 }); }); g.part("trunk", () => limb(g, [[32, 57], [32, 53], [31, 48], [32, 41]], 4.8, 3.2, { furrows: true, wobble: 0.1, ph: 1.9 }));}function barkMarks(g) { g.part("barkMarks", () => { g.line([30, 49], [30, 51], "ink"); g.pixel(31, 52, "w0"); g.pixel(29, 48, "w3"); // a knot with a lit lip g.pixel(28, 55, "w3"); g.pixel(27, 57, "w3"); // lit ridges on the flare g.pixel(35, 46, "w0"); g.pixel(36, 53, "w0"); });}// ---- ground: fallen-leaf ring and grass ---------------------------------------------------------------------const LEAF_KEYS = [["r2", "r1"], ["r3", "r2"], ["g2", "g1"], ["r1", "r0"], ["w3", "w2"], ["g1", "g0"]];const LITTER = [];for (let i = 0; i < 34; i++) { const a = (i / 34) * TAU + hash(i, 3) * 0.25; const rr = 0.72 + hash(i, 7) * 0.38; LITTER.push([Math.round(CX + Math.cos(a) * 22 * rr), Math.round(59 + Math.sin(a) * 3.3 * rr), LEAF_KEYS[i % 6]]);}function litter(g, apple) { g.part("litter", () => { for (const [x, y] of [[13, 60], [19, 58], [47, 57], [52, 60], [26, 62], [41, 62]]) { // grass blades g.pixel(x, y, "e1"); g.pixel(x, y - 1, "e2"); g.pixel(x + 1, y, "e1"); } for (const [x, y, k] of LITTER) { if (apple && (x + y) % 3 === 0) continue; // fewer leaves under the apple tree g.pixel(x, y, k[0]); g.pixel(x + 1, y, k[1]); if ((x * 7 + y) % 4 === 0) g.pixel(x, y + 1, k[1]); } });}// ---- falling leaves -----------------------------------------------------------------------------------------// a spinning 2px leaf: flat, tipped, edge-on, tipped the other wayfunction leafShape(g, x, y, k, spin, big) { if (spin === 0) { g.pixel(x, y, k[0]); g.pixel(x + 1, y, k[1]); if (big) g.pixel(x - 1, y - 1, k[0]); } else if (spin === 1) { g.pixel(x, y, k[0]); g.pixel(x + 1, y + 1, k[1]); if (big) g.pixel(x - 1, y, k[0]); } else if (spin === 2) { g.pixel(x, y, k[0]); g.pixel(x, y + 1, k[1]); } else { g.pixel(x, y + 1, k[0]); g.pixel(x + 1, y, k[1]); }}// falls from inside the crown (hidden behind it) and lands exactly on a litter leaf, so it vanishes into the ringfunction fallingLeaf(g, t, x0, y0, li, ph, sway) { const [lx, ly, k] = LITTER[li]; const q = (((t + ph) % 1) + 1) % 1, F = 0.72; if (q >= F) return; const s = q / F; const x = Math.round(x0 + (lx - x0) * s + sway * Math.sin(TAU * s * 1.5) * Math.sin(Math.PI * s)); const y = Math.round(y0 + (ly - y0) * (0.35 * s + 0.65 * s * s)); leafShape(g, x, y, k, s > 0.88 ? 0 : Math.floor(q * 40) % 4, s <= 0.88);}// ---- apples ---------------------------------------------------------------------------------------------------const APPLE = [ "..s...", ".KsKK.", "K5221K", "K2211K", "K1110K", ".KKKK.",];const AK = { s: "w1", K: "ink", "5": "n5", "2": "r2", "1": "r1", "0": "r0" };function apple(g, x, y, squash) { APPLE.forEach((row, j) => { if (squash && j === 0) return; [...row].forEach((ch, i) => { if (ch !== ".") g.pixel(x + i, y + j + (squash && j < 2 ? 1 : 0), AK[ch]); }); });}// apples among the leaves: [clump index, dx, dy] (top-left of the sprite, relative to the clump centre)const HANGING = [[0, -4, 0], [4, 2, -2], [7, -3, 1], [8, 1, -1], [11, -1, 0], [10, -5, -2], [3, -2, 1]];const DROP = { clump: 13, x: 37, y: 38 }; // the apple that falls (sits under clump 13)const TUFT = { x: 46, y: 54 };function tuft(g) { g.part("tuft", () => { const blades = [[0, 3, "e1"], [1, 6, "e2"], [2, 4, "e1"], [3, 7, "e3"], [4, 5, "e2"], [5, 7, "e2"], [6, 4, "e1"], [7, 5, "e2"], [8, 2, "e1"]]; for (const [dx, h, k] of blades) { for (let j = 0; j < h; j++) g.pixel(TUFT.x + dx, TUFT.y + 7 - j, j < 2 ? "e0" : k); } g.pixel(TUFT.x + 3, TUFT.y + 1, "e3"); g.pixel(TUFT.x + 1, TUFT.y + 2, "e3"); });}// 16f drop choreography: [dx, dy, squash] from the hanging spot; null = hidden (it has rolled behind the tuft)const DROP_PATH = [ [0, 0], [0, 0], [0, 0], [1, 0], [0, 1], [0, 4], [0, 9], [0, 14], [0, 17, 1], [1, 14], [2, 13], [3, 15], [4, 17], [6, 17], [8, 17], [10, 17],];const REGROW = { 14: -4, 15: -2 }; // a new apple lowering into view from the leaves// ---- crown motion -------------------------------------------------------------------------------------------const hF = (cy) => Math.max(0, Math.min(1, (40 - cy) / 32));function swayIdle(t) { return CLUMPS.map((c) => [Math.round(1.25 * hF(c[1]) * Math.sin(TAU * (t + c[1] * 0.012))), 0]);}function swayGust(t) { return CLUMPS.map((c) => { const h = hF(c[1]); const lean = h * (3.6 + 1.3 * Math.sin(TAU * (2 * t + c[1] * 0.02))) + 0.8; return [Math.round(lean), h > 0.5 ? 1 : 0]; });}// gust flurry: [speed (crossings per loop), phase, base y, wave amplitude, colour]const FLURRY = [ [1, 0.0, 12, 3, 0], [1, 0.33, 24, 4, 1], [2, 0.1, 18, 2, 2], [1, 0.66, 36, 3, 3], [2, 0.55, 30, 3, 1], [1, 0.18, 46, 2, 2], [2, 0.8, 8, 2, 0], [1, 0.5, 52, 2, 4], [1, 0.85, 20, 3, 2], [2, 0.3, 42, 3, 0], [1, 0.12, 30, 2, 5], [2, 0.67, 14, 2, 1],];function flurry(g, t) { g.detached("flurry", () => { FLURRY.forEach(([sp, ph, by, amp, ki], i) => { const p = (t * sp + ph) % 1; const x = Math.round(-6 + p * 76); const y = Math.round(by + amp * Math.sin(TAU * (t * 2 + ph * 3)) + (p - 0.5) * 6); leafShape(g, x, y, LEAF_KEYS[ki], Math.floor((t * N_GUST * 2 + i)) % 4, i % 2 === 0); }); });}const AutumnTree = sprite(pal, (g, t, mode) => { t = ((t % 1) + 1) % 1; const isApple = mode === "apples"; const offs = mode === "gust" ? swayGust(t) : swayIdle(t); contactShadow(g, CX, GROUND, 18, 2); litter(g, isApple); g.outlined("ink", () => tree(g)); barkMarks(g); let f = 0; if (isApple) { f = Math.round(t * N_APPLE) % N_APPLE; // the dropping apple is drawn behind the crown so it hangs under (and lowers out of) the leaves const o = offs[DROP.clump]; g.detached("dropApple", () => { const p = DROP_PATH[f]; const attached = f < 4; apple(g, DROP.x + p[0] + (attached ? o[0] : 0), DROP.y + p[1], p[2]); if (REGROW[f] !== undefined) apple(g, DROP.x + o[0], DROP.y + REGROW[f]); }); } g.detached("leaves", () => { if (mode === "gust") return; fallingLeaf(g, t, 20, 30, isApple ? 1 : 3, 0.0, 3); fallingLeaf(g, t, 44, 29, 5, 0.38, -3); if (!isApple) fallingLeaf(g, t, 30, 33, 7, 0.7, 2); }); g.outlined("ink", () => crown(g, offs, isApple ? 4 : 3)); if (isApple) { g.detached("apples", () => { for (const [ci, dx, dy] of HANGING) { const c = CLUMPS[ci], o = offs[ci]; apple(g, Math.round(c[0] + o[0] + dx), Math.round(c[1] + o[1] + dy)); } }); tuft(g); } if (mode === "gust") flurry(g, t);});submit(AutumnTree, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, anchor: { x: CX, y: GROUND }, variants: [ { name: "idle", args: ["idle"], animate: { fps: 10, duration: N_IDLE / 10, loop: true } }, { name: "gust", args: ["gust"], animate: { fps: 10, duration: N_GUST / 10, loop: true } }, { name: "apples", args: ["apples"], animate: { fps: 10, duration: N_APPLE / 10, 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.