Pumpkin patch
A 48×32 floor prop: a raised bed of tilled earth with a furrowed top and a darker front lip, three uncarved pumpkins of different sizes, broad palmate leaves and curling vines with tendrils. The objects are drawn nearest-first inside g.depth, so each keeps its own ink outline against the ones behind it. “idle” (12 frames) rustles the leaves, each on its own phase, and curls the tendrils; “grow” (10 frames, once) swells the middle pumpkin from a green bud to full size; “harvested” (12 frames) leaves cut stems and hollows with one small pumpkin.
6×
Get 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.
// Pumpkin patch — 48x32 floor prop, top-down 3/4, key light top-left.// A raised bed of tilled earth (furrowed top face, darker front lip) with three uncarved pumpkins of different sizes,// broad palmate leaves and curling vines with tendrils.// Variants:// idle 12f loop: the leaves rustle in a breeze (each on its own phase), the tendrils curl and uncurl// grow 10f once: the middle pumpkin swells from a green bud to full size, the leaves unfurl; ends on idle f0// harvested 12f loop: cut stem stumps and hollows where pumpkins were, one small pumpkin left, leaves rustle// Objects are drawn nearest-first inside g.depth so each one keeps its own ink outline against the ones behind it.The last lines. submit declares the canvas and lists the variants the renderer exports.
submit(PumpkinPatch, { size: [W, H], 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: "grow", args: ["grow"], animate: { fps: 10, duration: N_GROW / 10, loop: false } }, { name: "harvested", args: ["harvested"] }, ],});Show the whole program (249 lines)
// Pumpkin patch — 48x32 floor prop, top-down 3/4, key light top-left.// A raised bed of tilled earth (furrowed top face, darker front lip) with three uncarved pumpkins of different sizes,// broad palmate leaves and curling vines with tendrils.// Variants:// idle 12f loop: the leaves rustle in a breeze (each on its own phase), the tendrils curl and uncurl// grow 10f once: the middle pumpkin swells from a green bud to full size, the leaves unfurl; ends on idle f0// harvested 12f loop: cut stem stumps and hollows where pumpkins were, one small pumpkin left, leaves rustle// Objects are drawn nearest-first inside g.depth so each one keeps its own ink outline against the ones behind it.const W = 48, H = 32, GROUND = 29, CX = 24;const N_IDLE = 12, N_GROW = 10;const BED = { cx: 24, cy: 20, rx: 21, ry: 6.5 };const PUMP = ["r0", "r1", "r2", "r3", "g2"];const BUD = ["e0", "e1", "e2", "e3", "e3"];// ---- the bed ------------------------------------------------------------------------------------------------function bed(g) { const { cx, cy, rx, ry } = BED; g.part("bed", () => { // front lip of the raised bed (the earth wall facing the camera) g.field({ x: [cx - rx, cx + rx], y: [cy - ry, cy + ry + 3] }, (x, y) => { const nx = (x + 0.5 - cx) / rx; for (let d = 0; d <= 3; d++) { const ny = (y + 0.5 - d - cy) / ry; if (nx * nx + ny * ny <= 1) { if (d === 0) return null; // top face drawn below return nx < -0.55 ? "w1" : "w0"; } } return null; }); // top face: furrows running across, ridges catch the light, left side warmer g.field({ x: [cx - rx, cx + rx], y: [cy - ry, cy + ry] }, (x, y) => { const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry; const r2 = nx * nx + ny * ny; if (r2 > 1) return null; // furrows run across the bed, gently wavy so they read as hand-hoed earth, not planks const wav = Math.round(0.6 * Math.sin(x * 0.55 + Math.floor(y / 3) * 2.1)); const row = ((y + wav - Math.round(cy - ry) + 30) % 3); const lit = -0.6 * nx - 0.4 * ny; if (r2 > 0.8 && ny > 0.3) return chk(x, y) ? "w2" : "w1"; // crumbly near rim if (row === 0) return lit > 0.3 ? "w3" : "w2"; // ridge top if (row === 1) return "w1"; return lit > 0.35 && chk(x, y) ? "w1" : "w0"; // furrow (softened on the lit side) }); // clods and stones: countable marks for (const [x, y, k] of [[7, 19, "w3"], [11, 24, "n3"], [19, 25, "w3"], [30, 24, "n2"], [40, 20, "w2"], [43, 21, "w3"], [5, 21, "n3"], [36, 15, "w3"]]) g.pixel(x, y, k); });}// ---- pumpkin --------------------------------------------------------------------------------------------------// k: 0..1 growth (1 = ripe orange; small k = a green bud that ripens through the ramp swap point)function pumpkin(g, name, cx, base, rx, ry, lobes, opts) { const o = opts || {}; const ramp = o.green ? BUD : PUMP; const cy = base - ry; g.part(name, () => { g.field({ x: [Math.floor(cx - rx - 1), Math.ceil(cx + rx + 1)], y: [Math.floor(cy - ry - 1), Math.ceil(base)] }, (x, y) => { const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry; // squat body with ribbed bumps along the silhouette const s = nx / Math.sqrt(Math.max(0.08, 1 - Math.min(0.95, ny * ny))); const lob = (Math.asin(Math.max(-1, Math.min(1, s))) / (Math.PI / 2) + 1) * lobes / 2; const fr = lob - Math.floor(lob); const bump = 1 + 0.07 * Math.sin(Math.PI * fr); if (nx * nx + ny * ny > bump * bump) return null; let i = litIndex(x, y, cx, cy, rx * bump, ry * bump, 5, 0.12); if (i < 0) i = 0; // each lobe is a little barrel: its left half catches more light if (fr < 0.38 && i < 4) i += 0; else if (fr > 0.62 && i > 0) i -= 1; // groove between lobes if ((fr < 0.14 || fr > 0.9) && Math.abs(ny) < 0.85) i = Math.max(0, i - 2); // dimple at the crown if (ny < -0.55 && Math.abs(nx) < 0.22) i = Math.max(0, i - 1); // specular on the upper-left lobe if (!o.green && rx > 4) { const sx = nx + 0.42, sy = ny + 0.42; if (sx * sx * 4 + sy * sy * 9 < 0.12) i = 4; } return ramp[Math.max(0, Math.min(4, i))]; }); }); // stem if (o.stem !== false) { const sx = Math.round(cx), sy = Math.round(cy - ry); g.part(name + "Stem", () => { if (rx >= 5) { g.pixel(sx, sy, "w1"); g.pixel(sx - 1, sy, "w2"); g.pixel(sx, sy - 1, "w2"); g.pixel(sx + 1, sy - 1, "w1"); g.pixel(sx + 1, sy - 2, "w3"); g.pixel(sx + 2, sy - 2, "w2"); } else { g.pixel(sx, sy, "w2"); g.pixel(sx + 1, sy - 1, "w3"); } }); }}// cut stem stump left in the bed after harvest, with a shallow hollow where the fruit satfunction hollow(g, name, cx, base, rx) { // the shallow dent the fruit left in the earth: dark far wall, lit near lip g.part(name, () => { g.ellipse({ x: [cx - rx, cx + rx], y: [base - 3, base] }, "w0", { fill: "w0" }); g.line([cx - rx + 2, base - 3], [cx + rx - 2, base - 3], "ink"); g.line([cx - rx + 1, base], [cx + rx - 1, base], "w3"); g.pixel(cx - rx + 1, base - 1, "w2"); g.pixel(cx + rx - 1, base - 1, "w1"); });}function stump(g, name, cx, base) { // the cut stem: a short woody stub with a pale fresh-cut face, a curl of vine still attached g.part(name, () => { g.pixel(cx, base, "w1"); g.pixel(cx + 1, base, "w0"); g.pixel(cx, base - 1, "w2"); g.pixel(cx + 1, base - 1, "w1"); g.pixel(cx, base - 2, "w4"); g.pixel(cx + 1, base - 2, "w3"); });}// ---- leaves ---------------------------------------------------------------------------------------------------// a palmate pumpkin leaf seen from above-front: 5 rounded lobes, three veins, lit top-leftfunction leaf(g, name, cx, cy, R, ang, scale, wilt) { const r = R * scale; if (r < 0.9) return; const ry = 0.72; // foreshortened by the 3/4 view g.part(name, () => g.field({ x: [Math.floor(cx - r - 1), Math.ceil(cx + r + 1)], y: [Math.floor(cy - r - 1), Math.ceil(cy + r + 1)] }, (x, y) => { const dx = x + 0.5 - cx, dy = (y + 0.5 - cy) / ry; const rho = Math.hypot(dx, dy) / r; const th = Math.atan2(dy, dx) - ang; const edge = 0.86 + 0.14 * Math.cos(5 * th); const notch = Math.cos(th) < -0.9 ? 0.55 : 1; // the stalk notch at the base if (rho > edge * notch) return null; // veins from the base if (r >= 3 && rho < 0.85) for (const va of [0, 1.15, -1.15]) { let d = th - va; d = Math.atan2(Math.sin(d), Math.cos(d)); if (Math.abs(d) * rho * r < 0.5 && rho > 0.15) return "e1"; } const l = (-0.65 * dx - 0.55 * dy * ry) / r; if (wilt && rho > 0.62) return l > 0 ? "g1" : "w2"; // late-season leaf: browned, curling edge // the leaf is a shallow cup: its far rim is lit, its near half falls in shade return l > 0.25 ? "e3" : l > -0.25 ? "e2" : rho > 0.55 ? "e1" : "e2"; }));}// ---- vines and tendrils -------------------------------------------------------------------------------------function vine(g, name, path) { g.part(name, () => g.sweep(path, { w0: 0.7, w1: 0.45 }, (u, v) => (v < -0.3 ? "e2" : "e1")));}function tendril(g, x, y, dir, turns, curl, key) { // a spiral that tightens toward its tip; curl (0..1) winds it up for (let s = 0; s <= 1.001; s += 0.08) { const rad = 2.6 * (1 - s * 0.7); const a = dir * (s * TAU * turns * (0.65 + 0.35 * curl)) - Math.PI / 2; g.pixel(Math.round(x + dir * s * 2 + Math.cos(a) * rad - Math.cos(-Math.PI / 2) * 2.6), Math.round(y + Math.sin(a) * rad * 0.8 + 2), s > 0.7 ? "e3" : key); }}// ---- scene ------------------------------------------------------------------------------------------------------// leaf table: [cx, cy, R, ang, phase, depthOrder] (bigger y = nearer)const LEAVES = [ [6, 15, 4.6, -2.3, 0.0], [17, 13, 4.2, -1.6, 0.3], [33, 13, 4.6, -1.2, 0.55], [42, 16, 4.0, -0.6, 0.8], [20, 25, 3.8, 1.9, 0.15], [31, 24, 4.4, 1.2, 0.65], [43, 23, 3.4, 0.6, 0.4],];function scene(g, t, mode, grow) { const gk = grow === undefined ? 1 : grow; // growth of the middle pumpkin (1 = ripe) const unf = grow === undefined ? 1 : 0.35 + 0.65 * Math.min(1, grow * 1.25); const harvested = mode === "harvested"; contactShadow(g, CX, GROUND, 21, 1); // layer depths: nearer = more negative; objects are drawn front-to-back const Z = (y) => -Math.round(y); // front leaves first (they overlap the pumpkins) LEAVES.forEach(([x, y, R, a, ph], i) => { if (y < 20) return; const sw = 0.22 * Math.sin(TAU * (t + ph)); const s = harvested ? (i === 4 ? 0.7 : 0.9) : (i === 4 ? unf : 1); g.depth(Z(y + 6), () => g.outlined("ink", () => leaf(g, "leafF" + i, x, y + Math.round(sw * 0.6), R, a + sw, s, harvested && i !== 6))); }); if (!harvested) { // big pumpkin, front left g.depth(Z(27), () => g.outlined("ink", () => pumpkin(g, "pumpkinBig", 12, 27, 7.5, 5.2, 5))); // middle pumpkin, back centre — it is the one that grows const k = gk; const rx = 2 + 4.6 * k, ry = 1.6 + 3.6 * k; g.depth(Z(22), () => g.outlined("ink", () => pumpkin(g, "pumpkinMid", 26, 21, rx, ry, k > 0.5 ? 5 : 3, { green: k < 0.45, stem: true }))); } else { g.depth(Z(27), () => g.outlined("ink", () => stump(g, "stumpBig", 12, 24))); g.depth(Z(22), () => g.outlined("ink", () => stump(g, "stumpMid", 26, 19))); g.depth(Z(11), () => { hollow(g, "hollowBig", 12, 26, 6); hollow(g, "hollowMid", 26, 21, 4); }); } // small pumpkin, right (the one left after the harvest) g.depth(Z(25), () => g.outlined("ink", () => pumpkin(g, "pumpkinSmall", 37, 25, 4.6, 3.4, 3))); // vines: thin runners lying on the earth (no outline; dark green reads against the brown) g.depth(Z(14), () => { vine(g, "vineL", [[8, 22], [4, 20], [4, 17], [7, 15]]); vine(g, "vineM", [[17, 22], [20, 24], [24, 24], [28, 22], [31, 20]]); vine(g, "vineR", [[31, 20], [35, 18], [39, 18], [43, 19]]); vine(g, "vineB", [[22, 18], [20, 15], [16, 14]]); }); // back leaves LEAVES.forEach(([x, y, R, a, ph], i) => { if (y >= 20) return; const sw = 0.2 * Math.sin(TAU * (t + ph)); const s = harvested ? (i === 1 ? 0.75 : 0.95) : (i === 1 || i === 2 ? unf : 1); g.depth(Z(y), () => g.outlined("ink", () => leaf(g, "leafB" + i, x, y + Math.round(sw * 0.6), R, a + sw, s, harvested && (i === 0 || i === 2)))); }); g.depth(Z(10), () => g.outlined("ink", () => bed(g))); // tendrils: thin spirals drawn on top, no outline g.depth(-60, () => g.detached("tendrils", () => { const c1 = 0.5 + 0.5 * Math.sin(TAU * t), c2 = 0.5 + 0.5 * Math.sin(TAU * (t + 0.5)); tendril(g, 3, 12, 1, 1.1, c1, "e2"); tendril(g, 44, 12, -1, 1.0, c2, "e2"); if (!harvested) tendril(g, 22, 21, -1, 0.9, c2, "e2"); }));}const PumpkinPatch = sprite(pal, (g, t, mode) => { t = ((t % 1) + 1) % 1; if (mode === "grow") { const f = Math.min(N_GROW - 1, Math.round(t * N_GROW)); const p = f / (N_GROW - 1); const k = 1 - Math.pow(1 - p, 2.2); // fast swell, settles into full size // a small overshoot near the end gives the swell some weight, back to exactly 1 on the last frame const ko = f === N_GROW - 2 ? 1.06 : f === N_GROW - 1 ? 1 : k; scene(g, 0, "idle", ko); return; } const f = Math.round(t * N_IDLE) % N_IDLE; scene(g, f / N_IDLE, mode);});submit(PumpkinPatch, { size: [W, H], 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: "grow", args: ["grow"], animate: { fps: 10, duration: N_GROW / 10, loop: false } }, { name: "harvested", args: ["harvested"] }, ],});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.