Wheat sheaf
A 32×48 floor prop of a ripe wheat sheaf tied with a red ribbon and twine, with a crescent sickle leaning on its right side. In the 16-frame idle loop, a breeze runs across the golden ears as ribbon tails stir and grains fall. The gust variant loops over 12 frames as strong wind bends the crown and stalks tumble, while festival is a 16-frame loop dressed with a straw star, flowers, and bows.
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.
// Wheat sheaf — 32x48 floor prop, top-down 3/4, key light top-left.// A tall hand-bound sheaf of ripe wheat standing on stubble: straw stalks pinched at the waist by a red ribbon// and twine, flaring out at the foot, a crown of heavy golden ears with whiskery awns, and a sickle (wooden grip,// iron crescent blade) leaning against its right side.// Variants:// idle 16f loop: a breeze runs across the ears (each ear nods a little after its neighbour), the crown leans// and returns, the ribbon tails stir, a couple of grains drift off and fall// gust 12f loop: a strong wind bends the crown hard to the right with a fast flutter; three loose stalks// tumble across the canvas (they leave on the right and come back in on the left), chaff streams// festival 16f loop: corn-dolly dressing for the fair: a woven straw star crowns the sheaf, a big red bow with// long streamers (and a blue one) flutters, poppies and cornflowers are tucked into the tieThe last lines. submit declares the canvas and lists the variants the renderer exports.
submit(WheatSheaf, { 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: "gust", args: ["gust"], animate: { fps: 10, duration: N_GUST / 10, loop: true } }, { name: "festival", args: ["festival"] }, ],});Show the whole program (287 lines)
// Wheat sheaf — 32x48 floor prop, top-down 3/4, key light top-left.// A tall hand-bound sheaf of ripe wheat standing on stubble: straw stalks pinched at the waist by a red ribbon// and twine, flaring out at the foot, a crown of heavy golden ears with whiskery awns, and a sickle (wooden grip,// iron crescent blade) leaning against its right side.// Variants:// idle 16f loop: a breeze runs across the ears (each ear nods a little after its neighbour), the crown leans// and returns, the ribbon tails stir, a couple of grains drift off and fall// gust 12f loop: a strong wind bends the crown hard to the right with a fast flutter; three loose stalks// tumble across the canvas (they leave on the right and come back in on the left), chaff streams// festival 16f loop: corn-dolly dressing for the fair: a woven straw star crowns the sheaf, a big red bow with// long streamers (and a blue one) flutters, poppies and cornflowers are tucked into the tieconst W = 32, H = 48, GROUND = 45, CX = 16;const WAIST = 31, TOP = 19;const N_IDLE = 16, N_GUST = 12;const L = [-0.74, -0.67];const STRAW = ["w1", "w2", "g1", "w4", "g2"];const EAR_F = ["g0", "g1", "g2", "g3"], EAR_B = ["w1", "g0", "g1", "g2"];// ---- the bundle of stalks -------------------------------------------------------------------------------------const halfW = (y) => y >= WAIST ? 3.4 + 4.3 * Math.pow((y - WAIST) / (GROUND - 1 - WAIST), 1.4) : 3.4 + 3.2 * Math.pow((WAIST - y) / (WAIST - TOP), 1.15);const centre = (y, bend) => CX + (y < WAIST ? bend * Math.pow((WAIST - y) / (WAIST - TOP), 2) : 0);function stalks(g, bend) { g.part("stalks", () => g.field({ x: [5, 27], y: [TOP - 1, GROUND] }, (x, y) => { const hw = halfW(y), u = (x + 0.5 - centre(y, bend)) / hw; const sid = Math.floor(((u + 1) / 2) * 9); // ragged cut ends at the foot: alternate stalks reach one pixel lower if (y >= GROUND - 1 && (sid % 2 === 1 || y === GROUND)) return null; if (Math.abs(u) > 1) return null; const f = ((u + 1) / 2) * 9 - sid; const lit = -0.85 * u + 0.35 * Math.sqrt(1 - u * u) - (y > WAIST ? 0 : 0.1); let k = lit > 0.6 ? 3 : lit > 0.15 ? 2 : lit > -0.35 ? 1 : 0; if (f < 0.22 && k > 0) k--; // the gap between two stalks if (y === GROUND - 2 && k < 3) k++; // light catching the cut ends if (y < WAIST && y > WAIST - 3 && k > 0) k--; // pinched just above the tie if (k === 3 && f > 0.55 && f < 0.8 && (y % 5 === 1)) return STRAW[4]; // a node glinting on a lit stalk return STRAW[k]; }));}function stubble(g, gust, front) { const lean = Math.round(gust * 1.2); g.part(front ? "stubbleFront" : "stubbleBack", () => { const list = front ? [[4, 2], [7, 3], [10, 1], [21, 2], [24, 3], [27, 2]] : [[3, 2], [6, 1], [9, 3], [12, 2], [20, 3], [23, 1], [26, 2], [28, 1]]; for (const [x, h] of list) { const yb = front ? GROUND + 1 : GROUND - 1; g.line([x, yb], [x + (h > 1 ? lean : 0), yb - h], h > 2 ? "w3" : "w2"); g.pixel(x + (h > 1 ? lean : 0), yb - h, "g1"); } });}// ---- ears ----------------------------------------------------------------------------------------------------const EARS = [];for (let i = 0; i < 11; i++) { const s = (i / 10) * 2 - 1; EARS.push({ s, front: i % 2 === 0, len: 6.2 + (1 - Math.abs(s)) * 1.3, lift: (1 - s * s) * 3 });}function earGeom(e, bend, swayAt) { const by = TOP + 1 - e.lift + (e.front ? 0 : -1.5); const bx = centre(by, bend) + e.s * 5.4; const th = e.s * 0.78 + swayAt(e.s); // radians from vertical, + = to the right const droop = e.s * 0.35 + swayAt(e.s) * 0.5; // the heavy head nods further const d1 = [Math.sin(th), -Math.cos(th)], d2 = [Math.sin(th + droop), -Math.cos(th + droop)]; const p0 = [bx, by], p1 = [bx + d1[0] * e.len * 0.5, by + d1[1] * e.len * 0.5]; const p2 = [p1[0] + d2[0] * e.len * 0.55, p1[1] + d2[1] * e.len * 0.55]; return { p0, p1, p2, dir: d2 };}function ear(g, e, bend, swayAt) { const { p0, p1, p2 } = earGeom(e, bend, swayAt); const dx = p2[0] - p0[0], dy = p2[1] - p0[1], len = Math.hypot(dx, dy) || 1; const nl = (-dy / len) * L[0] + (dx / len) * L[1]; // does +v face the light? const ramp = e.front ? EAR_F : EAR_B; // a short bare neck of stalk, then the plump head of grain g.line([Math.round(p0[0]), Math.round(p0[1])], [Math.round(p1[0]), Math.round(p1[1])], e.front ? "w4" : "g1"); g.sweep([[p0[0] + (p1[0] - p0[0]) * 0.45, p0[1] + (p1[1] - p0[1]) * 0.45], p1, p2], { w0: 1.25, w1: 0.6, profile: 0.7 }, (u, v) => { let l = v * nl * 1.3 + 0.3 - u * 0.25; if (Math.abs(v) > 0.55 && v * nl < 0) l -= 0.5; // the shadow flank parts it from its neighbour if (Math.floor(u * 6) % 2 === 1 && Math.abs(v) < 0.5) l -= 0.35; // rows of kernels down the middle return ramp[l > 0.75 ? 3 : l > 0.15 ? 2 : l > -0.45 ? 1 : 0]; });}function awns(g, bend, swayAt, front) { g.detached(front ? "awnsFront" : "awnsBack", () => { for (const e of EARS) { if (e.front !== front) continue; const { p2, dir } = earGeom(e, bend, swayAt); const a = [Math.round(p2[0] + dir[0] * 1.2), Math.round(p2[1] + dir[1] * 1.2)]; const b = [Math.round(p2[0] + dir[0] * 3.4 + dir[1] * 0.8), Math.round(p2[1] + dir[1] * 3.4 - dir[0] * 0.8)]; g.line(a, b, front ? "w4" : "g1"); } });}function crown(g, bend, swayAt) { g.outlined("ink", () => g.part("earsBack", () => { for (const e of EARS) if (!e.front) ear(g, e, bend, swayAt); })); awns(g, bend, swayAt, false);}function crownFront(g, bend, swayAt) { g.outlined("ink", () => g.part("earsFront", () => { for (const e of EARS) if (e.front) ear(g, e, bend, swayAt); })); awns(g, bend, swayAt, true);}// ---- tie, ribbon tails -----------------------------------------------------------------------------------------function tie(g, flutter) { g.part("tie", () => { for (let x = 12; x <= 20; x++) { const k = x < 14 ? "r2" : x < 18 ? "r1" : "r0"; g.pixel(x, WAIST, k); g.pixel(x, WAIST + 1, x < 16 ? "r1" : "r0"); } g.pixel(12, WAIST, "r3"); for (let x = 13; x <= 19; x += 2) g.pixel(x, WAIST - 1, "w3"); // twine wrap above the ribbon g.pixel(13, WAIST - 1, "w4"); }); g.part("bow", () => { // loops either side of a knot at the front-left g.fillPoly([[13, WAIST], [10, WAIST - 2], [10, WAIST + 1]], "r2"); g.fillPoly([[13, WAIST], [16, WAIST - 2], [16, WAIST + 1]], "r1"); g.pixel(10, WAIST - 2, "r3"); g.pixel(16, WAIST + 1, "r0"); g.rect({ x: [12, 13], y: [WAIST, WAIST + 1] }, "r0", { fill: "r0" }); g.pixel(12, WAIST, "r2"); }); g.part("tails", () => { // two short ribbon ends hanging from the knot, two pixels wide for (const [x0, ph, dir, n] of [[11, 0, -1, 5], [13, 0.3, 1, 4]]) { for (let i = 1; i <= n; i++) { const x = Math.round(x0 + dir * i * 0.3 + flutter(i / n, ph) * (i / n)); const y = WAIST + 1 + i; g.pixel(x, y, i === n ? "r1" : "r2"); g.pixel(x + 1, y, i === n ? "r0" : "r1"); } } });}// ---- sickle ----------------------------------------------------------------------------------------------------function sickle(g, festive) { g.part("sickleGrip", () => { g.line([26, 44], [24, 37], "w3"); g.line([27, 44], [25, 37], "w1"); g.line([26, 43], [25, 40], "w4"); g.pixel(27, 44, "w0"); if (festive) { g.pixel(25, 40, "b2"); g.pixel(26, 40, "b1"); g.pixel(24, 39, "b2"); } // a blue ribbon wound on the grip }); g.part("ferrule", () => { g.rect({ x: [23, 24], y: [35, 36] }, "n2", { fill: "n2" }); g.pixel(23, 35, "n4"); }); g.part("blade", () => g.sweep([[23.5, 34.5], [27, 31.5], [28.5, 27], [26, 23]], { w0: 1.3, w1: 0.45, profile: 0.8 }, (u, v) => { if (v < -0.45) return "n5"; // honed inner edge catches the light return v > 0.4 ? "n1" : u < 0.5 ? "n3" : "n2"; }));}// ---- drifting grains / loose stalks / chaff -----------------------------------------------------------------function grains(g, t, n, speed) { g.detached("grains", () => { for (let i = 0; i < n; i++) { const p = (t * speed + i / n) % 1; const x = Math.round(18 + i * 3 + p * 9 + Math.sin(TAU * (p + i * 0.3)) * 1.5), y = Math.round(12 + i * 2 + p * 26); if (p > 0.85 || x > W - 1) continue; g.pixel(x, y, p < 0.5 ? "g2" : "g1"); } });}function looseStalks(g, t) { // three whole stalks tumbling across on the wind (each one wraps from the right edge back to the left) g.outlined("ink", () => g.part("looseStalks", () => { for (let i = 0; i < 3; i++) { const p = (t + i / 3) % 1; const x = -9 + p * (W + 18), y = 7 + i * 10 + Math.sin(TAU * (p * 1.5 + i * 0.4)) * 3; const a = TAU * (p + i * 0.3); // end over end, once per crossing const c = Math.cos(a), sn = Math.sin(a) * 0.7; const s0 = [Math.round(x - c * 4), Math.round(y - sn * 4)], s1 = [Math.round(x + c * 1), Math.round(y + sn * 1)]; const e1 = [Math.round(x + c * 4), Math.round(y + sn * 4)]; g.line(s0, s1, "w3"); g.line(s1, e1, "g2"); g.line([s1[0], s1[1] - 1], [e1[0], e1[1] - 1], "g1"); // the plump head, two pixels thick g.pixel(Math.round(x + c * 2.5), Math.round(y + sn * 2.5) - (Math.abs(c) > 0.5 ? 1 : 0), "g1"); g.pixel(e1[0], e1[1], "g3"); } })); g.detached("chaff", () => { for (let i = 0; i < 4; i++) { // short chaff streaks const p = (t * 2 + i / 4) % 1; const x = Math.round(-2 + p * (W + 4)), y = 5 + ((i * 11) % 34); g.pixel(x, y, "w4"); g.pixel(x - 1, y, "g1"); } });}// ---- festival dressing -----------------------------------------------------------------------------------------function star(g, t, bend) { const c = [Math.round(centre(TOP, bend)), 6]; const spin = 0.12 * Math.sin(TAU * t); g.outlined("ink", () => { g.part("starStick", () => g.line([c[0], c[1] + 4], [Math.round(centre(TOP + 1, bend)), TOP - 3], "w3")); g.part("star", () => { for (let k = 0; k < 8; k++) { const a = spin + (k * TAU) / 8 - Math.PI / 2, long = k % 2 === 0; const r = long ? 5.6 : 3.8; const tip = [Math.round(c[0] + Math.cos(a) * r), Math.round(c[1] + Math.sin(a) * r)]; const lit = L[0] * Math.cos(a) + L[1] * Math.sin(a); g.line(c, tip, lit > 0.3 ? "g3" : lit > -0.3 ? "g2" : "g1"); } // woven ring tying the straws g.strokeEllipse({ x: [c[0] - 2, c[0] + 2], y: [c[1] - 2, c[1] + 2] }, "g1"); g.pixel(c[0] - 1, c[1] - 2, "g3"); g.pixel(c[0] + 2, c[1] + 1, "g0"); g.pixel(c[0], c[1], "r1"); }); });}function flowers(g) { g.part("flowers", () => { // poppies and cornflowers tucked under the ribbon for (const [x, y] of [[18, WAIST - 2], [20, WAIST - 1]]) { g.pixel(x, y, "r2"); g.pixel(x + 1, y, "r1"); g.pixel(x, y - 1, "r3"); g.pixel(x + 1, y + 1, "ink"); } for (const [x, y] of [[9, WAIST - 1], [15, WAIST - 3]]) { g.pixel(x, y, "b2"); g.pixel(x - 1, y, "b1"); g.pixel(x, y - 1, "b3"); } g.pixel(17, WAIST - 3, "e2"); g.pixel(10, WAIST - 2, "e1"); });}function streamers(g, t) { // a red and a blue streamer from the bow, two pixels wide, rippling as the breeze runs down them g.outlined("ink", () => g.part("streamers", () => { for (const [lit, dark, ph, x0, dir] of [["r2", "r1", 0, 10, -1], ["b2", "b1", 0.4, 14, 1]]) { for (let i = 1; i <= 11; i++) { const q = i / 11; const wave = Math.sin(TAU * (t - q * 0.9 + ph)); const x = Math.round(x0 + dir * q * 2.5 + wave * 1.6 * q), y = WAIST + 1 + i; const face = Math.cos(TAU * (t - q * 0.9 + ph)) > -0.2; // the ribbon twists: lit face or back g.pixel(x, y, face ? lit : dark); g.pixel(x + 1, y, face ? dark : (lit === "r2" ? "r0" : "b0")); } } }));}// ---- assembly --------------------------------------------------------------------------------------------------function sheaf(g, t, cfg) { contactShadow(g, CX, GROUND, 11, 2); g.outlined("ink", () => stubble(g, cfg.gust, false)); crown(g, cfg.bend, cfg.sway); g.outlined("ink", () => stalks(g, cfg.bend)); crownFront(g, cfg.bend, cfg.sway); g.outlined("ink", () => { tie(g, cfg.flutter); }); if (cfg.festival) flowers(g); g.outlined("ink", () => sickle(g, cfg.festival)); g.outlined("ink", () => stubble(g, cfg.gust, true));}const WheatSheaf = sprite(pal, (g, t, mode) => { t = ((t % 1) + 1) % 1; if (mode === "gust") { const wob = Math.sin(TAU * 3 * t); // fast flutter on a hard lean const bend = 2 + Math.round(wob * 0.6); sheaf(g, t, { bend, gust: 1, sway: (s) => 0.5 + 0.12 * Math.sin(TAU * 3 * t - s * 2) - s * 0.35 - Math.max(0, s) * 0.15, flutter: (q, ph) => 2.2 + Math.sin(TAU * (3 * t - q + ph)) * 1.1, }); looseStalks(g, t); grains(g, t, 3, 2); return; } const breeze = Math.sin(TAU * t); const bend = Math.round(breeze * 1.1); const cfg = { bend, gust: breeze * 0.5, sway: (s) => 0.13 * Math.sin(TAU * t - s * 1.3), flutter: (q, ph) => Math.sin(TAU * (t - q * 0.8 + ph)) * 0.9, festival: mode === "festival", }; sheaf(g, t, cfg); if (cfg.festival) { star(g, t, bend); streamers(g, t); } else grains(g, t, 2, 1);});submit(WheatSheaf, { 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: "gust", args: ["gust"], animate: { fps: 10, duration: N_GUST / 10, loop: true } }, { name: "festival", args: ["festival"] }, ],});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.