Hay bales
A 48×48 floor prop of three tied straw bales stacked together, with loose straw at the base and a pitchfork leaning against the right side. In the 16-frame idle loop, straw wisps flutter while a stray strand blows past. The 12-frame mouse variant shows a field mouse peeking from between the bales, while the 16-frame harvest variant adds a basket of red apples and fluttering leaves.
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.
// Hay bales — 48x48 floor prop, top-down 3/4, key light top-left.// Three square straw bales (two side by side on the ground, one stacked on top and set back), each tied with two// bands of dark twine that pinch the straw, strand marks along their length, a lit top face and a banded front.// Loose straw lies at the base and a wooden pitchfork (iron tines up) leans against the right of the stack.// Variants:// idle 16f loop: straw wisps on the bales and the loose straw at the base flutter; a stray strand blows past// mouse 12f loop: a field mouse peeks out of a gap low between the bottom bales, twitches its nose, ducks back// harvest 16f loop: the same stack with a wicker basket of red apples on top and a little pile of fallen leaves;// autumn leaves flutter down and aroundThe last lines. submit declares the canvas and lists the variants the renderer exports.
submit(HayBales, { 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: "mouse", args: ["mouse"], animate: { fps: 10, duration: N_MOUSE / 10, loop: true } }, { name: "harvest", args: ["harvest"], animate: { fps: 10, duration: N_HARV / 10, loop: true } }, ],});Show the whole program (283 lines)
// Hay bales — 48x48 floor prop, top-down 3/4, key light top-left.// Three square straw bales (two side by side on the ground, one stacked on top and set back), each tied with two// bands of dark twine that pinch the straw, strand marks along their length, a lit top face and a banded front.// Loose straw lies at the base and a wooden pitchfork (iron tines up) leans against the right of the stack.// Variants:// idle 16f loop: straw wisps on the bales and the loose straw at the base flutter; a stray strand blows past// mouse 12f loop: a field mouse peeks out of a gap low between the bottom bales, twitches its nose, ducks back// harvest 16f loop: the same stack with a wicker basket of red apples on top and a little pile of fallen leaves;// autumn leaves flutter down and aroundconst W = 48, H = 48, GROUND = 45, CX = 24;const N_IDLE = 16, N_MOUSE = 12, N_HARV = 16;// bales: [x0, x1, yTop] — top face rows yTop..yTop+5, front face rows yTop+6..yTop+15const TOP_ROWS = 6, FRONT_ROWS = 10;const BALE_L = [3, 20, 29], BALE_R = [22, 38, 29], BALE_T = [11, 28, 16];const HOLE = { x0: 19, x1: 23, y0: 39, y1: 44 };// ---- a bale ------------------------------------------------------------------------------------------------------const FRONT_BAND = [1.6, 1.45, 1.4, 1.3, 1.05, 1.0, 0.9, 0.55, 0.45, 0.0]; // per row: lit edge, body, shaded basefunction bale(g, name, [x0, x1, yT]) { const yF = yT + TOP_ROWS, yB = yF + FRONT_ROWS - 1; const ties = [x0 + 4, x1 - 4]; g.part(name, () => { g.field({ x: [x0, x1], y: [yT, yB] }, (x, y) => { if ((y === yT || y === yB) && (x === x0 || x === x1)) return null; // rounded corners const u = (x - x0) / (x1 - x0); if (y < yF) { // top face, in the light const r = y - yT; const strand = (x * 2 + y * 5) % 9 < 2 && r % 2 === 1 ? 0.35 : (x + y * 4) % 11 === 0 ? -0.4 : 0; const v = 0.75 - u * 0.75 + (r === TOP_ROWS - 1 ? 0.35 : 0) - (r === 0 ? 0.45 : 0) + strand; return v > 0.8 ? "g3" : v > 0.0 ? "g2" : "g1"; } const r = y - yF; // horizontal strand dashes on alternate rows, plus a few dark gaps between flakes const strand = r % 2 === 1 && (x + y * 5) % 9 < 2 ? 0.45 : (x * 3 + y * 7) % 13 === 0 ? -0.5 : 0; const v = FRONT_BAND[r] - u * 0.45 + (x === x0 ? 0.4 : 0) + strand; return v > 1.75 ? "g2" : v > 0.85 ? "g1" : v > 0.25 ? "g0" : "w1"; }); // twine: over the top face and down the front; the straw bulges beside it (lit left, pinched dark right) for (const tx of ties) { for (let y = yT + 1; y < yB; y++) { const front = y >= yF; g.pixel(tx, y, front ? (y < yF + 4 ? "w1" : "w0") : "w2"); if (y > yT + 1 && y < yB - 1) { g.pixel(tx - 1, y, front ? (y < yF + 5 ? "g2" : "g1") : "g3"); g.pixel(tx + 1, y, front ? (y < yF + 3 ? "g0" : "w1") : "g1"); } } g.pixel(tx, yF, "w3"); // the band turns over the edge g.pixel(tx, yT, "w1"); } });}// wisps: loose strands standing up off the bales, rooted on the surface; tips swing with the windconst WISPS = [ // [rootX, rootY, dx, dy, key, phase] [13, 18, -1, -3, "g2", 0], [15, 17, 0, -3, "g3", 0.1], [14, 18, -3, -2, "g1", 0.2], [23, 17, 1, -3, "g2", 0.15], [27, 18, 2, -3, "g3", 0], [26, 17, 1, -2, "g1", 0.3], [5, 30, -1, -3, "g2", 0.25], [7, 30, 0, -2, "g3", 0.05], [4, 31, -3, -1, "g1", 0.3], [33, 30, 1, -3, "g2", 0.2], [36, 30, 2, -2, "g3", 0.1], [38, 32, 3, -1, "g1", 0], [3, 39, -2, 0, "g2", 0.15], [38, 38, 3, -1, "g2", 0.35], [29, 21, 2, -1, "g3", 0.05],];function wisps(g, t, amp) { g.part("wisps", () => { for (const [x, y, dx, dy, k, ph] of WISPS) { const sw = Math.round(Math.sin(TAU * (t - ph)) * amp * (Math.abs(dy) >= 3 ? 1.2 : 0.7)); const tx = x + dx + sw, ty = y + dy + (Math.abs(sw) > 1 ? 1 : 0); g.line([x, y], [tx, ty], k); } });}function looseStraw(g, wind) { const d = Math.round(wind * 0.8); g.part("looseStraw", () => { for (const [a, b, k] of [[[2, 45], [7, 44], "g1"], [[6, 46], [10, 46], "g2"], [[14, 46], [18, 45], "g1"], [[25, 46], [29, 46], "g0"], [[30, 45], [34, 46], "g2"], [[0, 43], [2, 45], "g0"], [[40, 46], [43, 45], "g1"]]) { g.line(a, [b[0] + d, b[1]], k); } g.pixel(7 + d, 44, "g3"); g.pixel(34 + d, 46, "g3"); });}function strayStrand(g, t) { // one strand tumbling across on the breeze, once per loop (off-canvas for the rest) if (t > 0.7) return; const q = t / 0.7; const x = Math.round(-2 + q * 52), y = Math.round(26 - q * 10 + Math.sin(TAU * q * 2) * 3); const up = Math.floor(q * 8) % 2; g.detached("strayStrand", () => { g.line([x, y], [x + 2, y + (up ? -1 : 1)], "g2"); g.pixel(x + 2, y + (up ? -1 : 1), "g3"); });}// ---- pitchfork ----------------------------------------------------------------------------------------------------function pitchfork(g) { g.part("forkHandle", () => { g.line([44, 44], [32, 10], "w2"); g.line([43, 43], [39, 32], "w3"); // lit edge along the shaft g.line([38, 27], [34, 16], "w3"); g.pixel(44, 44, "w1"); }); g.part("forkHead", () => { g.line([31, 9], [32, 10], "n2"); // ferrule // crossbar, then three tines leaning with the shaft (3px apart so the gaps survive the outline) g.line([27, 9], [35, 7], "n2"); g.pixel(27, 9, "n3"); g.pixel(28, 9, "n4"); for (const [bx, by] of [[27, 9], [31, 8], [35, 7]]) { g.line([bx, by], [bx - 2, by - 6], bx === 27 ? "n4" : "n3"); g.pixel(bx - 2, by - 6, "n4"); } });}// ---- mouse gap ---------------------------------------------------------------------------------------------------function hole(g) { const { x0, x1, y0, y1 } = HOLE; g.part("gap", () => { g.fillPoly([[x0, y1], [x0, y0 + 2], [x0 + 2, y0], [x1 - 1, y0], [x1, y0 + 2], [x1, y1]], "n0"); g.line([x0 + 1, y0 + 1], [x1 - 1, y0 + 1], "ink"); g.pixel(x0, y0 + 2, "w0"); g.pixel(x1, y0 + 2, "w0"); for (const [x, k] of [[x0 + 1, "g1"], [x0 + 3, "g0"], [x1 - 1, "g1"]]) g.pixel(x, y0, k); // straw fringe });}// e: 0 hidden .. 1 fully out; nose: 0/1 twitch; ear: 0/1 flick; blinkconst MOUSE_ROWS = [ // [dy, dx0, keys] — head facing left, ty = top of the head [0, 2, ["w4", "w4", "w3", "w3", "w2"]], [1, 1, ["w4", "w4", "w4", "w3", "w3", "w2", "w1"]], [2, 0, ["w4", "w4", "w3", "w3", "w3", "w2", "w2", "w1"]], [3, 1, ["n5", "n5", "n4", "w2", "w2", "w1", "w1"]], // pale chin and chest];function mouse(g, e, nose, ear, blink) { if (e <= 0) return; const nx = 21 - Math.round(e * 6), ty = 40; // snout tip x, head top y const px = (x, y, k) => { if (x <= HOLE.x1 - 1) g.pixel(x, y, k); }; // the rest is still inside the gap g.outlined("ink", () => g.part("mouse", () => { for (const [dy, dx0, ks] of MOUSE_ROWS) ks.forEach((k, i) => px(nx + dx0 + i, ty + dy, k)); px(nx + 4 + ear, ty - 1, "w3"); px(nx + 5 + ear, ty - 1, "w2"); // round ear, flicks back px(nx + 4 + ear, ty - 2, "w3"); px(nx + 5 + ear, ty - 2, "w2"); })); g.part("mouseFace", () => { px(nx + 4 + ear, ty - 1, "v3"); // pink inner ear px(nx - 1, ty + 2 - nose, "v3"); // pink nose, twitching px(nx + 3, ty + 1, blink ? "w2" : "ink"); // bead eye if (!blink) px(nx + 3, ty, "n5"); }); if (e >= 1) g.detached("whiskers", () => { g.line([nx - 2, ty + 1 - nose], [nx - 3, ty - nose], "n4"); g.pixel(nx - 2, ty + 3, "n3"); });}// ---- harvest basket + leaves ---------------------------------------------------------------------------------------function basket(g) { g.part("basketHandle", () => { g.curve([[13, 13], [13, 5], [19, 1], [25, 5], [25, 13]], "w2"); g.curve([[13, 11], [14, 5], [19, 2]], "w3"); }); g.part("apples", () => { // back row first, then the front row; each apple rimmed in dark red so the heap reads as separate fruit for (const [ax, ay] of [[17, 8], [21, 8], [14, 11], [19, 10], [24, 11]]) { g.outlined("r0", () => g.field({ x: [ax - 2, ax + 2], y: [ay - 2, ay + 2] }, (x, y) => { const i = litIndex(x, y, ax + 0.5, ay + 0.5, 2.4, 2.3, 4, 0.1); return i < 0 ? null : ["r1", "r1", "r2", "r2"][i]; })); g.pixel(ax - 1, ay - 1, "r3"); g.pixel(ax + 1, ay + 1, "r0"); } g.pixel(16, 7, "g3"); g.pixel(13, 10, "g3"); g.pixel(18, 9, "g3"); // glints of the key light g.pixel(19, 6, "w1"); g.pixel(20, 5, "e2"); g.pixel(21, 5, "e1"); // stem + leaf on the top apple }); g.part("basket", () => { g.field({ x: [13, 26], y: [13, 20] }, (x, y) => { const inset = Math.floor((y - 13) / 4); if (x < 13 + inset || x > 26 - inset) return null; if (y <= 14) return x < 17 ? "w4" : x < 23 ? "w3" : "w2"; // rim const u = (x - 13) / 13; // wicker: horizontal withies (alternate rows lit) woven round darker upright stakes const v = 0.6 - u * 1.1 - (y - 13) / 12 + (y % 2 ? 0.25 : -0.1) + (x % 3 === 0 ? -0.35 : 0); return v > 0.45 ? "w3" : v > -0.25 ? "w2" : v > -0.7 ? "w1" : "w0"; }); g.line([14, 15], [25, 15], "w1"); // shadow under the rim }); g.part("basketShadow", () => { g.line([26, 20], [27, 20], "g1"); g.pixel(27, 19, "g1"); });}const LEAF_KEYS = ["r2", "r3", "r1", "g1", "r2", "r3"];const LEAF_POSES = [ [[0, 0, 0], [1, 0, 0], [1, 1, 1], [2, 1, 0]], // flat, broadside [[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 2, 1]], // tipped [[0, 0, 1], [1, 0, 0], [2, -1, 0]], // edge-on];function leaves(g, t) { g.detached("leaves", () => { for (let i = 0; i < 6; i++) { const p = (t + i / 6) % 1; const x0 = [6, 40, 17, 31, 44, 25][i]; const x = Math.round(x0 + Math.sin(TAU * (p * 1.5 + i * 0.37)) * 4 - p * 3); const y = Math.round(-3 + p * 50); if (y > 45) continue; const pose = LEAF_POSES[Math.floor(p * 12 + i) % 3]; // tumbling const k = LEAF_KEYS[i], d = i === 2 ? "r0" : "w1"; for (const [dx, dy, dark] of pose) g.pixel(x + dx, y + dy, dark ? d : k); } });}// a small pile of fallen leaves: overlapping clumps, each on its own rampconst CLUMPS = [ [8, 44, 3.2, 1.9, ["w1", "w2", "w3", "w4"]], [4, 45, 2.8, 1.7, ["r0", "r1", "r2", "r3"]], [11, 46, 2.6, 1.5, ["g0", "g1", "g2", "g3"]],];function leafPile(g) { g.outlined("ink", () => g.part("leafPile", () => { for (const [cx, cy, rx, ry, ramp] of CLUMPS) { g.field({ x: [Math.floor(cx - rx), Math.ceil(cx + rx)], y: [Math.floor(cy - ry), Math.ceil(cy + ry)] }, (x, y) => { const i = litIndex(x, y, cx, cy, rx, ry, 4, 0.1); if (i < 0) return null; return ramp[(x + y) % 4 === 0 ? Math.max(0, i - 1) : i]; // leaf edges inside the clump }); } })); g.part("strayLeaves", () => { g.pixel(38, 46, "g1"); g.pixel(39, 46, "r2"); g.pixel(31, 33, "r1"); g.pixel(32, 33, "r2"); // one resting on the right bale });}// ---- scene --------------------------------------------------------------------------------------------------------function stack(g, wind) { contactShadow(g, CX, GROUND, 22, 2); g.outlined("ink", () => bale(g, "baleL", BALE_L)); g.outlined("ink", () => bale(g, "baleR", BALE_R)); g.part("seam", () => { // where the lower bales meet: the right bale's end catches light, the left one's falls dark for (let y = 36; y <= 43; y++) { g.pixel(22, y, y < 39 ? "g2" : "g1"); g.pixel(20, y, "w1"); } }); g.part("stackShadow", () => { // the top bale casts a shadow down-right onto the lower top faces g.line([12, 33], [29, 33], "g1"); g.line([12, 34], [29, 34], "g2"); for (let y = 29; y <= 33; y++) { g.pixel(30, y, "g1"); g.pixel(29, y, "g0"); } }); g.outlined("ink", () => bale(g, "baleT", BALE_T)); g.part("contactLine", () => g.line([12, 33], [28, 33], "w1")); // dark crease under the top bale looseStraw(g, wind);}function scene(g, t, mode) { const wind = Math.sin(TAU * t); stack(g, wind * 1.2); wisps(g, t, 1.3); if (mode === "mouse") { hole(g); const f = Math.round(t * N_MOUSE) % N_MOUSE; const E = [0, 0, 0.34, 0.67, 1, 1, 1, 1, 1, 1, 0.67, 0.34][f]; mouse(g, E, f === 5 || f === 7 ? 1 : 0, f === 6 ? 1 : 0, f === 8); } if (mode === "harvest") { leafPile(g); g.outlined("ink", () => basket(g)); } g.outlined("ink", () => pitchfork(g)); if (mode === "idle") strayStrand(g, t); if (mode === "harvest") leaves(g, t);}const HayBales = sprite(pal, (g, t, mode) => { t = ((t % 1) + 1) % 1; scene(g, t, mode);});submit(HayBales, { 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: "mouse", args: ["mouse"], animate: { fps: 10, duration: N_MOUSE / 10, loop: true } }, { name: "harvest", args: ["harvest"], animate: { fps: 10, duration: N_HARV / 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.