Tessarune
Harvest Festival · beehive.js

Beehive

32×48 · 240 lines of JavaScript

A 32×48 floor prop featuring a coiled straw skep on a low wooden stand alongside a clay honey pot with a grooved wooden dipper. In the 16-frame idle loop, three bees with beating wings circle the entrance on independent paths. The swarm variant is a 12-frame loop where a dozen bees whirl around the skep, while the 16-frame honey variant shows a droplet dripping from the dipper as a lone bee hovers nearby.

Beehive — Rendered output6×
Rendered by this program, variant “honey” · 32×48 · 16 frames
Harvest FestivalGet 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.

beehive.js · 1–9
// Beehive — 32x48 floor prop, top-down 3/4, key light top-left.// A straw skep (a bell of coiled straw bands that curve with the dome, a twisted knot on top, a dark arched entrance// at the foot) on a short wooden stand; beside it on the ground a clay honey pot with a twine tie, honey dribbling// over the lip and a grooved wooden dipper standing in it, head up.// Variants://   idle   16f loop: three bees (gold + ink, beating wings) circle the entrance on separate paths//   swarm  12f loop: a dozen agitated bees whirl around the whole skep, some passing behind it//   honey  16f loop: a drop of honey swells on the dipper head, stretches, falls into the pot and rings the surface;//                    the glint on the honey slides; one lazy bee hovers by the entrance

The last lines. submit declares the canvas and lists the variants the renderer exports.

beehive.js · 230–240
submit(Beehive, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 16, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "swarm", args: ["swarm"], animate: { fps: 10, duration: N_SWARM / 10, loop: true } },    { name: "honey", args: ["honey"], animate: { fps: 10, duration: N_HONEY / 10, loop: true } },  ],});
Show the whole program (240 lines)
beehive.js · 240 lines
// Beehive — 32x48 floor prop, top-down 3/4, key light top-left.// A straw skep (a bell of coiled straw bands that curve with the dome, a twisted knot on top, a dark arched entrance// at the foot) on a short wooden stand; beside it on the ground a clay honey pot with a twine tie, honey dribbling// over the lip and a grooved wooden dipper standing in it, head up.// Variants://   idle   16f loop: three bees (gold + ink, beating wings) circle the entrance on separate paths//   swarm  12f loop: a dozen agitated bees whirl around the whole skep, some passing behind it//   honey  16f loop: a drop of honey swells on the dipper head, stretches, falls into the pot and rings the surface;//                    the glint on the honey slides; one lazy bee hovers by the entranceconst W = 32, H = 48, GROUND = 45;const N_IDLE = 16, N_SWARM = 12, N_HONEY = 16;const SKEP = { cx: 11, base: 31, h: 19, rx: 8.6 };const ENTR = { x: 11, y: 29 };const POT = { cx: 25, top: 34, bottom: 44, rx: 5 };const DIP = { hx: 27, hy: 23 };                     // dipper head centre// ---- stand --------------------------------------------------------------------------------------------------------function stand(g) {  g.part("standLegs", () => {    for (const [x0, lit] of [[3, true], [17, false]]) {      for (let y = 35; y <= 44; y++) {        g.pixel(x0, y, lit ? "w3" : "w2");        g.pixel(x0 + 1, y, lit ? "w2" : "w1");      }      g.pixel(x0 + 1, 37, "w1");                                                  // a knot in the grain    }    g.line([5, 39], [16, 39], "w1");                                             // rear stretcher, in shadow  });  g.part("standTop", () => {    for (let x = 1; x <= 21; x++) {      const u = (x - 1) / 20;      g.pixel(x, 31, u < 0.3 ? "w4" : "w3");      g.pixel(x, 32, u < 0.6 ? "w3" : "w2");      g.pixel(x, 33, u < 0.2 ? "w2" : "w1");                                     // front edge of the plank      g.pixel(x, 34, "w0");    }    for (const x of [7, 14]) g.pixel(x, 33, "w0");                                // plank joins    g.pixel(19, 32, "w1"); g.pixel(4, 32, "w4");  });}// ---- skep ---------------------------------------------------------------------------------------------------------const BAND = 3;function skepHalfWidth(y) {  const { base, h, rx } = SKEP;  const k = (base - y) / h;                                                       // 0 at the foot .. 1 at the crown  if (k < 0 || k > 1) return -1;  return rx * Math.pow(Math.max(0, 1 - Math.pow(k, 2.1)), 0.5) + (k < 0.12 ? 0.5 : 0);}function skep(g) {  const { cx, base, h } = SKEP;  g.part("skep", () => g.field({ x: [cx - 10, cx + 10], y: [base - h, base] }, (x, y) => {    const hw = skepHalfWidth(y);    if (hw < 0.6) return null;    const nx = (x + 0.5 - cx) / hw;    if (Math.abs(nx) > 1) return null;    const k = (base - y) / h;    // coils are rings seen from above: the band lines dip toward the front centre    const yy = y - 1.6 * Math.sqrt(Math.max(0, 1 - nx * nx));    const r = (((Math.floor(base - yy) % BAND) + BAND) % BAND);                  // 0 groove .. 2 crown of the coil    let v = -0.85 * nx + 0.5 * k - 0.1;    v += r === 2 ? 0.45 : r === 1 ? 0.1 : -0.55;    // stitches binding the coils: short dark ticks on the groove, staggered per band    const band = Math.floor((base - yy) / BAND);    if (r === 0 && (x + band * 2) % 5 === 0) return "w1";    if (Math.abs(nx) > 0.93) v -= 0.35;                                           // the dome turns away at the edge    return v > 0.75 ? "g3" : v > 0.15 ? "g2" : v > -0.45 ? "g1" : v > -0.95 ? "g0" : "w1";  }));  g.part("skepKnot", () => {    // twisted straw knot at the crown    g.pixel(cx - 1, base - h - 1, "g2"); g.pixel(cx, base - h - 1, "g1");    g.pixel(cx - 1, base - h - 2, "g3"); g.pixel(cx, base - h - 2, "g2");    g.pixel(cx, base - h, "g0");  });  g.part("entrance", () => {    const { x, y } = ENTR;    g.fillPoly([[x - 2, y + 2], [x - 2, y], [x - 1, y - 1], [x + 1, y - 1], [x + 2, y], [x + 2, y + 2]], "ink");    g.line([x - 3, y + 2], [x - 3, y], "g0"); g.pixel(x - 2, y - 1, "g0");         // straw lip of the arch    g.pixel(x - 1, y - 2, "g3"); g.pixel(x, y - 2, "g2"); g.pixel(x + 1, y - 2, "g1");    g.pixel(x, y + 1, "n0");                                                      // a little depth inside  });}// ---- honey pot + dipper -------------------------------------------------------------------------------------------function pot(g) {  const { cx, top, bottom, rx } = POT;  g.part("pot", () => {    g.field({ x: [cx - rx - 1, cx + rx + 1], y: [top + 2, bottom] }, (x, y) => {      // squat belly: widest two-thirds of the way down      const k = (y - top - 2) / (bottom - top - 2);      const hw = 3.6 + 2.0 * Math.sin(Math.PI * Math.min(1, k * 1.15)) - (k > 0.9 ? 0.8 : 0);      const nx = (x + 0.5 - cx) / hw;      if (Math.abs(nx) > 1) return null;      const nz = Math.sqrt(1 - nx * nx);      const v = -0.6 * nx + 0.5 * nz - k * 0.35;      return v > 0.55 ? "w4" : v > 0.15 ? "w3" : v > -0.3 ? "w2" : v > -0.65 ? "w1" : "w0";    });    // rim: a lit ring, honey in the mouth    g.ellipse({ x: [cx - 4, cx + 4], y: [top, top + 3] }, "w2", { fill: "w2" });    g.line([cx - 3, top], [cx + 1, top], "w4"); g.pixel(cx - 4, top + 1, "w3");    g.ellipse({ x: [cx - 3, cx + 3], y: [top + 1, top + 2] }, "g1", { fill: "g1" });    g.line([cx - 2, top + 1], [cx + 1, top + 1], "g2");    g.pixel(cx + 3, top + 3, "w1"); g.pixel(cx + 4, top + 2, "w1");    // twine tie under the rim    for (let x = cx - 4; x <= cx + 4; x++) g.pixel(x, top + 4, x < cx - 1 ? "w3" : x < cx + 2 ? "w1" : "w0");    g.pixel(cx - 3, top + 5, "w3"); g.pixel(cx - 4, top + 6, "w1");               // dangling end    // honey dribbling over the lip    g.pixel(cx - 1, top + 3, "g2"); g.pixel(cx - 1, top + 4, "g2"); g.pixel(cx - 1, top + 5, "g1");    g.pixel(cx, top + 3, "g1"); g.pixel(cx - 1, top + 6, "g1"); g.pixel(cx - 2, top + 5, "g3");  });}function dipper(g) {  const { hx, hy } = DIP;  g.part("dipperHandle", () => {    // the handle runs down-left from the head into the honey    g.line([hx - 1, hy + 3], [hx - 4, POT.top + 1], "w3");    g.line([hx, hy + 3], [hx - 3, POT.top + 1], "w1");  });  g.part("dipperHead", () => {    // grooved head, coated in honey: ridges lit on the left, grooves dark    for (let dy = -2; dy <= 2; dy++) {      for (let dx = -1; dx <= 1; dx++) {        const ridge = (dy + 2) % 2 === 0;        const k = ridge ? (dx < 0 ? "g3" : dx === 0 ? "g2" : "g1") : (dx < 0 ? "g1" : dx === 0 ? "g0" : "w1");        g.pixel(hx + dx, hy + dy, k);      }    }    g.pixel(hx, hy - 3, "w2"); g.pixel(hx, hy + 3, "g1");                            // knob on top, honey collar below  });}// honey drip, f in 0..N_HONEY-1; returns nothing when the cycle is between dropsfunction drip(g, f, still) {  const { hx, hy } = DIP, surf = POT.top + 1;  const x = hx + 1, y0 = hy + 3;                                                   // drips off the head's lower right  g.detached("drip", () => {    if (still) { g.pixel(x, y0, "g1"); return; }    // swell (0-5), stretch (6-8), fall (9-11), splash + rings (12-15)    if (f <= 5) {      g.pixel(x, y0, f < 3 ? "g1" : "g2");      if (f >= 3) { g.pixel(x, y0 + 1, "g2"); g.pixel(x + 1, y0 + 1, "g1"); }      if (f >= 5) { g.pixel(x, y0 + 2, "g1"); g.pixel(x, y0 + 1, "g3"); }    } else if (f <= 8) {      const len = f - 4;                                                           // 2..4      g.line([x, y0], [x, y0 + len - 1], "g1");      g.rect({ x: [x, x + 1], y: [y0 + len, y0 + len + 1] }, "g2", { fill: "g2" });      g.pixel(x, y0 + len, "g3"); g.pixel(x + 1, y0 + len + 1, "g1");    } else if (f <= 11) {      const y = y0 + 4 + (f - 9) * 3;      if (y < surf - 1) {        g.rect({ x: [x, x + 1], y: [y, y + 1] }, "g2", { fill: "g2" });        g.pixel(x, y, "g3"); g.pixel(x + 1, y + 1, "g1"); g.pixel(x, y - 1, "g1");      }      if (f === 9) g.pixel(x, y0, "g1");                                          // the thread snaps back    } else {      const r = f - 11;                                                            // 1..4 rings widen      if (r <= 3) {        g.pixel(x - r, surf, "g3"); g.pixel(Math.min(POT.cx + 3, x + r - 1), surf, r < 3 ? "g3" : "g2");      }      if (r === 1) g.pixel(x - 1, surf - 1, "g2");                                 // a small crown on impact    }  });}function honeyGlint(g, t) {  // a glint that slides slowly across the honey in the mouth  const x = POT.cx - 2 + Math.round(2.5 * (0.5 - 0.5 * Math.cos(TAU * t)));  g.part("honeyGlint", () => { g.pixel(x, POT.top + 1, "g3"); g.pixel(x + 1, POT.top + 1, "g2"); });}// ---- bees ---------------------------------------------------------------------------------------------------------// a bee: gold body with an ink stripe trailing behind its heading, a pale wing that beats every framefunction bee(g, x, y, dir, wingUp, name) {  x = Math.round(x); y = Math.round(y);  g.detached(name, () => {    g.pixel(x + dir, y, "g2");                                                     // head end    g.pixel(x, y, "ink");                                                          // stripe    g.pixel(x - dir, y, "g1");                                                     // abdomen    if (wingUp) { g.pixel(x, y - 1, "n5"); g.pixel(x - dir, y - 1, "n4"); } else g.pixel(x, y + 1, "n4");  });}function pathBee(g, fn, t, frame, name, behind) {  const [x, y, back] = fn(t);  const [x2] = fn(t + 0.004);  if (!!back !== !!behind) return;  bee(g, x, y, x2 >= x ? 1 : -1, (frame + name.length) % 2 === 0, name);}const IDLE_BEES = [  (t) => [ENTR.x + 7 * Math.cos(TAU * t), ENTR.y - 1 + 3 * Math.sin(TAU * t), false],                 // ring at the door  (t) => [SKEP.cx + 8 * Math.sin(TAU * -t + 1), 7 + 3 * Math.sin(TAU * -2 * t + 2), false],           // figure-eight over the crown  (t) => { const a = TAU * 2 * t + 2.2; return [SKEP.cx + 11 * Math.cos(a), 21 + 5 * Math.sin(a), Math.sin(a) < -0.2]; },];function swarmBee(i) {  const f1 = 1 + (i % 3), f2 = 2 + (i % 2), s = i % 2 ? 1 : -1;  const rx = 8 + (i * 37 % 6), ry = 3 + (i * 13 % 4), cy = 5 + (i * 7 % 23), ph = i * 0.61;  return (t) => {    const a = TAU * f1 * s * t + ph;    return [SKEP.cx + 1 + rx * Math.cos(a), cy + ry * Math.sin(TAU * f2 * t + ph * 1.7) * 0.6 + 1.5 * Math.sin(a), Math.sin(a) < -0.5 && cy > 15];  };}const SWARM = Array.from({ length: 12 }, (_, i) => swarmBee(i));// ---- scene --------------------------------------------------------------------------------------------------------const Beehive = sprite(pal, (g, t, mode) => {  t = ((t % 1) + 1) % 1;  const n = mode === "swarm" ? N_SWARM : mode === "honey" ? N_HONEY : N_IDLE;  const frame = Math.round(t * n) % n;  contactShadow(g, 11, GROUND, 10, 1);  contactShadow(g, POT.cx, GROUND, 5, 1);  const bees = mode === "swarm" ? SWARM : mode === "honey" ? [IDLE_BEES[0]] : IDLE_BEES;  bees.forEach((fn, i) => pathBee(g, fn, t, frame, "beeB" + i, true));             // bees behind the skep  g.outlined("ink", () => { stand(g); skep(g); });  g.outlined("ink", () => { pot(g); dipper(g); });  if (mode === "honey") { honeyGlint(g, t); drip(g, frame, false); }  else { honeyGlint(g, 0); drip(g, 0, true); }  // jitter for the swarm: a pixel of nervous wobble that repeats with the loop  bees.forEach((fn, i) => {    const jf = mode === "swarm" ? (tt) => { const p = fn(tt); const j = Math.round(hash(i, frame) * 2 - 1); return [p[0] + j, p[1] + (frame + i) % 2, p[2]]; } : fn;    pathBee(g, jf, t, frame, "bee" + i, false);  });});submit(Beehive, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 16, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "swarm", args: ["swarm"], animate: { fps: 10, duration: N_SWARM / 10, loop: true } },    { name: "honey", args: ["honey"], animate: { fps: 10, duration: N_HONEY / 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.