Tessarune
Harvest Festival · feast_table.js

Feast table

64×48 · 424 lines of JavaScript

This 64×48 harvest feast table is laden with a cider jug, cornucopia, cheese, a lit candle, bread, wine goblets, and an apple pie over a decorated cloth runner. In the 16-frame idle loop, steam curls from the pie as the candle flickers. The 12-frame toast loop lifts the goblets to clink over the table while a bee loops over the fruit, and cleared displays empty dishes and a tipped goblet over 16 frames.

Feast table — Rendered output6×
Rendered by this program, variant “toast” · 64×48 · 12 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.

feast_table.js · 1–13
// Feast table — 64x48 floor prop, top-down 3/4, key light top-left.// A long rustic harvest-feast trestle table: lengthwise planks, two trestle legs with block feet and a pegged// stretcher, a red cloth runner with gold borders whose ends drape over the table ends, and a garland of autumn// leaves swagged along the front edge. The spread (back row): an earthenware cider jug, a wicker cornucopia spilling// grapes, an apple and a small pumpkin, a cheese wheel with a cut wedge, a taper candle in a brass holder. Front row:// a scored bread loaf, two pewter goblets of red wine, grapes, an apple and a striped gourd on the runner, and a// lattice apple pie in a tin dish. The candle pools warm light on the planks around it.// Variants://   idle     16f loop  steam curls up from the pie, the candle flame flickers and its light pool breathes//   toast    12f loop  the goblets lift off the table (shadows stay), meet over the fruit and clink, wine splashes,//                      the jug's cider sloshes, they set back down; a bee loops over the fruit all the while//   cleared  16f loop  after the feast: an empty pie dish, a bread heel, a rind of cheese, crumbs, one goblet tipped//                      over in a wine puddle, the cornucopia emptied, the candle burned to a stub and guttering

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

feast_table.js · 416–424
submit(FeastTable, {  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: "toast", args: ["toast"], animate: { fps: 10, duration: N_TOAST / 10, loop: true } },    { name: "cleared", args: ["cleared"], animate: { fps: 10, duration: N_CLEAR / 10, loop: true } },  ],});
Show the whole program (424 lines)
feast_table.js · 424 lines
// Feast table — 64x48 floor prop, top-down 3/4, key light top-left.// A long rustic harvest-feast trestle table: lengthwise planks, two trestle legs with block feet and a pegged// stretcher, a red cloth runner with gold borders whose ends drape over the table ends, and a garland of autumn// leaves swagged along the front edge. The spread (back row): an earthenware cider jug, a wicker cornucopia spilling// grapes, an apple and a small pumpkin, a cheese wheel with a cut wedge, a taper candle in a brass holder. Front row:// a scored bread loaf, two pewter goblets of red wine, grapes, an apple and a striped gourd on the runner, and a// lattice apple pie in a tin dish. The candle pools warm light on the planks around it.// Variants://   idle     16f loop  steam curls up from the pie, the candle flame flickers and its light pool breathes//   toast    12f loop  the goblets lift off the table (shadows stay), meet over the fruit and clink, wine splashes,//                      the jug's cider sloshes, they set back down; a bee loops over the fruit all the while//   cleared  16f loop  after the feast: an empty pie dish, a bread heel, a rind of cheese, crumbs, one goblet tipped//                      over in a wine puddle, the cornucopia emptied, the candle burned to a stub and gutteringconst W = 64, H = 48, CX = 32, GROUND = 45;const N_IDLE = 16, N_TOAST = 12, N_CLEAR = 16;const TOP = { x0: 3, x1: 60, y0: 20, y1: 30 };                    // table top faceconst CANDLE = { x: 57, base: 23 };const WOOD = ["w0", "w1", "w2", "w3", "w4"];// one-character codes for the little stamped objects (fruit, crumbs)const CH = {  a: "r3", b: "r2", c: "r1", d: "r0", p: "g3", q: "g2", r: "g1", s: "g0", u: "v3", v: "v2", w: "v1", x: "v0",  "0": "e0", "1": "e1", "2": "e2", "3": "e3", W: "w4", V: "w3", U: "w2", T: "w1", S: "w0",  N: "n5", M: "n4", L: "n3", J: "n2", I: "n1", H: "n0", K: "ink",};// ---- layers: objects standing ON the table get their own ink ring (g.outlined would melt them into the planks) --function layer() {  const m = new Map();  const put = (x, y, k) => { if (x >= 0 && y >= 0 && x < W && y < H) m.set(y * W + x, k); };  const stamp = (x, y, rows) => rows.forEach((row, j) => [...row].forEach((ch, i) => { if (ch !== ".") put(x + i, y + j, CH[ch]); }));  return { m, put, stamp };}function emit(g, name, m, ringKey) {  const ring = new Map();  let x0 = W, x1 = -1, y0 = H, y1 = -1;  const grow = (x, y) => { x0 = Math.min(x0, x); x1 = Math.max(x1, x); y0 = Math.min(y0, y); y1 = Math.max(y1, y); };  for (const i of m.keys()) {    const x = i % W, y = (i - x) / W;    grow(x, y);    for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {      if (nx < 0 || ny < 0 || nx >= W || ny >= H || m.has(ny * W + nx)) continue;      ring.set(ny * W + nx, ringKey); grow(nx, ny);    }  }  if (x1 < 0) return;  g.part(name, () => g.field({ x: [x0, x1], y: [y0, y1] }, (x, y) => m.get(y * W + x) ?? ring.get(y * W + x) ?? null));}// ---- the table ------------------------------------------------------------------------------------------------function trestles(g) {  g.part("stretcher", () => {    g.rect({ x: [12, 51], y: [36, 38] }, "w1", { fill: "w1" });    g.line([12, 36], [51, 36], "w2");    g.line([12, 38], [51, 38], "w0");    for (const x of [13, 50]) { g.line([x, 35], [x, 39], "w3"); g.pixel(x, 40, "w1"); }      // through-wedges  });  for (const [x, name] of [[8, "legL"], [52, "legR"]]) {    g.part(name, () => {      g.field({ x: [x, x + 3], y: [33, 41] }, (px, py) => {        const u = px - x;        if (py <= 34) return u < 2 ? "w1" : "w0";                  // in the table's shadow        return ["w3", "w2", "w2", "w1"][u];      });      // block foot: lit top, front face with end grain      g.rect({ x: [x - 2, x + 5], y: [41, 44] }, "w2", { fill: "w2" });      g.line([x - 2, 41], [x + 5, 41], "w3");      g.line([x - 2, 44], [x + 5, 44], "w1");      g.pixel(x - 2, 42, "w3"); g.pixel(x + 5, 43, "w1");      g.line([x, 43], [x + 2, 43], "w1"); g.pixel(x + 1, 42, "w3");    });  }}function poolAt(x, y, R) {  const d = Math.hypot(x + 0.5 - CANDLE.x, (y + 0.5 - CANDLE.base - 1) * 2);  return d < R * 0.5 ? 2 : d < R - 1.2 ? 1 : d < R && chk(x, y) ? 1 : 0;}function tableTop(g, R) {  const { x0, x1, y0, y1 } = TOP;  g.part("tabletop", () => g.field({ x: [x0, x1], y: [y0, y1 + 3] }, (x, y) => {    if (y > y1) return y === y1 + 1 ? (x < 40 ? "w3" : "w2") : y === y1 + 2 ? "w1" : "w0";   // front edge, underside    let k = x > 50 ? 2 : 3;    if (y === y0 && x < 44) k = 4;                                   // back rim catches the key light    if (y === 22 || y === 28) k -= 1;                                // plank seams    if (x === x0) k = 2;    if ((y === 21 && (x === 12 || x === 13)) || (y === 29 && x >= 30 && x <= 32) || (y === 25 && x === 46)) k -= 1;   // grain    return WOOD[Math.max(0, Math.min(4, k + poolAt(x, y, R)))];  }));}function runner(g) {  g.part("runner", () => {    g.field({ x: [TOP.x0, TOP.x1], y: [23, 27] }, (x, y) => (y === 23 || y === 27 ? "g1" : y === 24 ? (x < 40 ? "r2" : "r1") : y === 26 ? "r0" : "r1"));    for (let x = TOP.x0 + 2; x < TOP.x1; x += 4) g.pixel(x, 25, "g1");      // woven dots down the middle    // ends draped over the table ends, with fringe    for (const s of [-1, 1]) {      const ex = s < 0 ? TOP.x0 - 1 : TOP.x1 + 1;      g.fillPoly([[ex, 23], [ex, 33], [ex + s, 34], [ex + s, 24]], s < 0 ? "r1" : "r0");      g.line([ex, 24], [ex, 33], s < 0 ? "r2" : "r1");      g.pixel(ex, 35, "g1"); g.pixel(ex + s, 36, "g1"); g.pixel(ex, 36, "g0");    }  });}function garland(g) {  const keys = [["r2", "r1"], ["g2", "g1"], ["r3", "r2"], ["w3", "w2"], ["r1", "r0"]];  g.part("garland", () => {    const anchors = [4, 22, 41, 59];    const yAt = (x) => { const k = x < 22 ? 0 : x < 41 ? 1 : 2, a = anchors[k], b = anchors[k + 1]; return 31 + Math.round(3 * Math.sin(Math.PI * (x - a) / (b - a))); };    for (let x = anchors[0]; x <= anchors[3]; x++) g.pixel(x, yAt(x), "w0");          // the twine    for (let x = anchors[0] + 1, i = 0; x < anchors[3]; x += 3, i++) {                  // leaf clusters      const y = yAt(x), k = keys[i % 5];      g.pixel(x, y - 1, k[0]); g.pixel(x + 1, y - 1, k[0]); g.pixel(x, y, k[0]); g.pixel(x + 1, y, k[1]); g.pixel(x + 1, y + 1, k[1]);    }    for (const x of anchors) { g.pixel(x, 30, "r2"); g.pixel(x - 1, 31, "r2"); g.pixel(x + 1, 31, "r1"); g.pixel(x, 32, "r0"); g.pixel(x, 31, "r1"); }   // ribbon bows  });}// ---- back row -------------------------------------------------------------------------------------------------function jug(g, slosh) {  const L = layer(), put = L.put, cx = 9;  const hw = (y) => (y <= 10 ? 2 : y <= 12 ? 1 : y <= 13 ? 2 : y <= 14 ? 3 : y <= 21 ? 4 : y <= 22 ? 3 : 2);  for (let y = 10; y <= 23; y++) {    const w = hw(y);    for (let x = cx - w; x <= cx + w; x++) {      const u = (x - (cx - w) + 0.5) / (2 * w + 1);      let k = y <= 15 ? (u < 0.22 ? "n5" : u < 0.5 ? "n4" : u < 0.8 ? "n3" : "n2")      // cream salt glaze above        : (u < 0.22 ? "w4" : u < 0.5 ? "w3" : u < 0.8 ? "w2" : "w1");                    // bare stoneware below      if (y === 16) k = u < 0.22 ? "r3" : u < 0.55 ? "r2" : u < 0.85 ? "r1" : "r0";    // glaze band      if (y === 23) k = u < 0.4 ? "w1" : "w0";      put(x, y, k);    }  }  // mouth: dark rim with the cider inside; a pouring lip on the left  put(8, 10, "w0"); put(9, 10, slosh === 1 ? "g2" : "g1"); put(10, 10, slosh === 2 ? "g2" : "w0");  put(6, 10, "w4");  // handle on the shaded side  put(12, 12, "w2"); put(13, 13, "w1"); put(13, 14, "w1"); put(13, 15, "w1"); put(12, 16, "w1");  emit(g, "jug", L.m, "ink");  if (slosh === 2) g.detached("ciderDrop", () => { g.pixel(9, 8, "g2"); g.pixel(10, 7, "g3"); });}// the horn: a cone swept along a quadratic curve (tail curling up at the back-left, mouth opening to the right),// built in a layer so it keeps its own ink ring against the jug behind itconst HORN = { p0: [18, 10], p1: [13, 23], p2: [29, 19] };const hornAt = (u) => {  const { p0, p1, p2 } = HORN, k = 1 - u;  return [k * k * p0[0] + 2 * k * u * p1[0] + u * u * p2[0], k * k * p0[1] + 2 * k * u * p1[1] + u * u * p2[1]];};function horn(g, full) {  const LD = [-0.74, -0.67];  const S = [];  for (let i = 0; i <= 80; i++) { const u = i / 80; S.push([u, ...hornAt(u)]); }  const H = layer();  for (let y = 7; y <= 25; y++) for (let x = 11; x <= 32; x++) {    let best = null, bd = 1e9;    for (const s of S) { const d = (x + 0.5 - s[1]) ** 2 + (y + 0.5 - s[2]) ** 2; if (d < bd) { bd = d; best = s; } }    const u = best[0], r = 0.7 + 3.5 * Math.pow(u, 1.3);    if (Math.sqrt(bd) > r) continue;    const [ax, ay] = hornAt(Math.max(0, u - 0.01)), [bx, by] = hornAt(Math.min(1, u + 0.01));    const tl = Math.hypot(bx - ax, by - ay) || 1, nx = -(by - ay) / tl, ny = (bx - ax) / tl;    const v = ((x + 0.5 - best[1]) * nx + (y + 0.5 - best[2]) * ny) / r;   // -1..1 across the horn    let l = v * (nx * LD[0] + ny * LD[1]) * 1.3 + 0.25 - 0.25 * v * v;    const ring = (u * 10) % 1;    if (ring < 0.2) l -= 0.6;                                                  // woven rings    else if ((Math.floor(u * 10) + Math.floor((v + 1) * 2)) % 2) l -= 0.3;    // basket weave    H.put(x, y, WOOD[l > 0.75 ? 4 : l > 0.2 ? 3 : l > -0.35 ? 2 : l > -0.8 ? 1 : 0]);  }  // the mouth, seen from the side: a lit rim around the dark inside  for (let y = 15; y <= 23; y++) for (let x = 27; x <= 32; x++) {    const ex = (x + 0.5 - 29.8) / 2.4, ey = (y + 0.5 - 19.3) / 4.4;    const r = ex * ex + ey * ey;    if (r > 1) continue;    H.put(x, y, r > 0.45 ? (ey < -0.2 || ex < -0.5 ? "w4" : "w2") : ey < 0 ? "ink" : "w0");  }  emit(g, "cornucopia", H.m, "ink");  const F = layer();  if (full) {    F.stamp(30, 14, ["..1..", ".uvvw", "uvwvw", ".wvwx", "..wx."]);        // grapes hanging over the rim    F.stamp(33, 18, [".T..", "Nbbc", "bbcd", ".cd."]);                       // apple    F.stamp(36, 19, ["..1..", ".abc.", "abbcd", ".ccd."]);                   // small pumpkin  } else {    F.stamp(31, 20, ["T.", "vw", ".x"]);                                     // two grapes left on a stem  }  emit(g, "hornFruit", F.m, "ink");}function cheese(g, whole) {  const L = layer(), put = L.put;  if (whole) {    const cx = 47, cy = 18.5, rx = 4.6, ry = 1.9;    for (let y = 16; y <= 23; y++) for (let x = 42; x <= 52; x++) {      const nx = (x + 0.5 - cx) / rx;      if (Math.abs(nx) > 1) continue;      const s = Math.sqrt(1 - nx * nx), top = cy - ry * s, bot = cy + ry * s;      if (y + 0.5 < top || y + 0.5 > bot + 3) continue;      // the wedge cut out of the front-right: the cut faces show paler paste      if (nx > 0.05 && y + 0.5 > cy - 0.2) { if (nx < 0.35 || y + 0.5 > bot + 2.2) put(x, y, nx < 0.35 ? "g3" : "g2"); continue; }      if (y + 0.5 <= bot) { const ny = (y + 0.5 - cy) / ry; put(x, y, -0.6 * nx - 0.5 * ny > 0.45 ? "g3" : nx > 0.5 ? "g1" : "g2"); }      else put(x, y, y + 0.5 > bot + 2 ? (nx < -0.3 ? "w2" : "w1") : nx < -0.4 ? "g1" : "g0");    }    put(48, 20, "g1"); put(48, 22, "g1");                                    // holes in the cut face    emit(g, "cheese", L.m, "ink");    const Wd = layer();                                                      // the cut wedge, lying in front    Wd.stamp(50, 21, ["pq..", "pqqr", "UTTT"]);    emit(g, "wedge", Wd.m, "ink");  } else {    L.stamp(45, 21, ["..pq", "pqqr", "UUTT"]);                               // a rind of cheese left    emit(g, "cheese", L.m, "ink");  }}function flick(t, s) {  const dh = Math.round(0.75 * Math.sin(TAU * (2 * t + s)) + 0.45 * Math.sin(TAU * (3 * t + 2.3 * s)));  const lean = 0.7 * Math.sin(TAU * (t + s)) + 0.45 * Math.sin(TAU * (3 * t + 1.7 * s));  return { dh, lean };}function candleFlame(g, fx, fy, h, lean) {  for (let i = 0; i < h; i++) {    const k = (i + 0.5) / h, y = fy - i;    const ax = fx + Math.round(lean * k * k);    if (k > 0.12 && k < 0.62 && h > 4) {      const edge = k > 0.18 && k < 0.5 ? "fl1" : "fl0";      g.pixel(ax - 1, y, edge); g.pixel(ax + 1, y, edge);    }    g.pixel(ax, y, k < 0.12 ? "fl1" : k < 0.5 ? "fl3" : k < 0.68 ? "fl2" : k < 0.86 ? "fl1" : "fl0");  }}function candle(g, t, stub) {  const { x, base } = CANDLE;  const top = stub ? base - 3 : base - 10;  const L = layer(), put = L.put;  // brass holder: a dish with a ring handle on the shaded side  for (let xx = x - 3; xx <= x + 2; xx++) { put(xx, base, xx < x ? "g2" : "g1"); put(xx, base + 1, xx < x - 1 ? "g1" : "g0"); }  put(x - 3, base, "g3");  // taper: two columns, lit left  for (let y = top; y < base; y++) { put(x - 1, y, "n5"); put(x, y, y > base - 3 ? "w3" : "w4"); }  put(x, top, "n4");  if (stub) { put(x - 2, base - 1, "n5"); put(x + 1, base - 1, "w4"); put(x - 2, base - 2, "n5"); put(x + 1, base, "w4"); }   // melted wax  else { put(x - 2, top + 3, "n5"); put(x - 2, top + 4, "w4"); }  emit(g, "candle", L.m, "ink");  const f = flick(t, 0.2);  let h = (stub ? 4 : 6) + f.dh, lean = f.lean;  if (stub) {                                                                // guttering: it dips and leans hard    const fr = Math.round(t * N_CLEAR) % N_CLEAR;    if (fr === 5 || fr === 6 || fr === 12) h -= 2;    if (fr === 6 || fr === 13) lean += 1.2;  }  g.part("flame", () => {    g.pixel(x - 1, top - 1, "ink");    candleFlame(g, x - 1, top - 2, Math.max(2, h), lean);  });}// ---- front row ------------------------------------------------------------------------------------------------function bread(g, whole) {  const L = layer(), put = L.put;  const cx = whole ? 10.5 : 9.5, cy = 26.3, rx = whole ? 5.2 : 2.8, ry = 2.6;  for (let y = 22; y <= 29; y++) for (let x = 3; x <= 17; x++) {    const i = litIndex(x, y, cx, cy, rx, ry, 4, 0.05);    if (i < 0) continue;    put(x, y, ["w1", "w2", "w3", "w4"][i]);  }  if (whole) {    for (const sx of [7, 10, 13]) { put(sx, 25, "w1"); put(sx + 1, 24, "w1"); put(sx - 1, 26, "w2"); }   // scores    put(8, 24, "n5"); put(6, 25, "n5");                                      // flour  } else {    for (let y = 25; y <= 28; y++) { put(12, y, y === 25 ? "n5" : "w4"); put(11, y, "n5"); }   // the cut face    put(11, 27, "w4");  }  emit(g, "bread", L.m, "ink");}// pewter goblet: x0 = left column of the bowl, yb = foot rowfunction goblet(g, name, x0, yb, slosh, empty) {  const L = layer();  const wine = empty ? ["I", "H", "H"] : slosh === 1 ? ["c", "b", "d"] : slosh === 2 ? ["b", "c", "c"] : ["b", "c", "d"];  L.stamp(x0, yb - 7, [    "MNMLJ",    "M" + wine.join("") + "J",    "NMLJI",    "MMLJI",    ".MLI.",    "..L..",    "..J..",    ".MLJ.",  ]);  emit(g, name, L.m, "ink");}function tippedGoblet(g) {  const L = layer();  L.stamp(16, 25, [    "M.....",    "MLLMNM",    "LJLMLJ",    "J.IJJI",  ]);  emit(g, "gobletTipped", L.m, "ink");  g.part("winePuddle", () => { g.line([23, 29], [25, 29], "r0"); g.pixel(24, 28, "r1"); g.pixel(26, 29, "r0"); });}function spill(g, full) {  const F = layer();  if (full) {    F.stamp(24, 23, ["..1..", ".uvvw", "uvwvw", ".wvwx", "..wx."]);          // grapes    F.stamp(29, 25, [".T..", "Nbbc", "bbcd", ".cd."]);                       // apple    F.stamp(33, 25, ["1......", ".pq2qr.", "qq2qq2r", ".rs2rs."]);           // striped gourd  } else {    F.stamp(33, 25, ["1......", ".pq2qr.", "qq2qq2r", ".rs2rs."]);           // the gourd was only decoration    F.stamp(29, 26, [".T", "Nq", "q.", "Nq"]);                               // an apple core  }  emit(g, "spill", F.m, "ink");}function pie(g, full) {  const L = layer(), put = L.put;  const cx = 51.5, cy = 25.6, rx = 5.7, ry = 2.6;  for (let y = 22; y <= 30; y++) for (let x = 44; x <= 58; x++) {    const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry;    const r = nx * nx + ny * ny;    if (r <= 1) {      if (r > 0.58) {                                                         // crimped crust rim / tin rim        const lit = -0.6 * nx - 0.6 * ny > 0.2;        put(x, y, full ? (lit ? "w4" : (x & 1) ? "w3" : "w2") : lit ? "n4" : "n3");      } else if (full) {                                                       // lattice over apple filling        const lat = (x + y) % 3 === 0 || (x - y + 60) % 3 === 0;        put(x, y, lat ? (nx < 0.2 ? "w4" : "w3") : nx < -0.1 && ny < 0.2 ? "g1" : "g0");      } else put(x, y, ny < -0.2 ? "n1" : "n2");                               // the empty tin      continue;    }    // the dish's side below the rim    const s = Math.sqrt(Math.max(0, 1 - nx * nx));    if (Math.abs(nx) < 0.97 && y + 0.5 > cy + ry * s && y + 0.5 <= cy + ry * s + 1.6) put(x, y, nx < -0.3 ? "n3" : "n2");  }  if (!full) { for (const [x, y] of [[49, 25], [52, 26], [54, 25]]) put(x, y, "w3"); put(50, 26, "w4"); put(51, 26, "w3"); }   // crumbs, a bit of crust  emit(g, "pie", L.m, "ink");}function steam(g, t) {  g.detached("steam", () => {    for (let i = 0; i < 3; i++) {      const p = (t + i / 3) % 1;      const bx = [48, 51, 53][i], y0 = 22 - Math.round(p * 12);      const x = bx + Math.round(Math.sin(TAU * (p * 1.2 + i * 0.37)) * 2);      // a puff that thins out as it rises: 2x2, then a curl of two, then one faint pixel      if (p < 0.35) { g.rect({ x: [x, x + 1], y: [y0 - 1, y0] }, "n5", { fill: "n5" }); g.pixel(x + 1, y0, "n4"); }      else if (p < 0.7) { g.pixel(x, y0, "n4"); g.pixel(x + 1, y0, "n5"); }      else if (p < 0.9) g.pixel(x, y0, "n3");    }  });}function crumbs(g) {  g.part("crumbs", () => {    for (const [x, y, k] of [[17, 24, "w4"], [19, 25, "w3"], [21, 27, "w3"], [27, 24, "w4"], [38, 26, "w3"], [40, 24, "w4"], [43, 28, "w3"], [14, 29, "w3"], [5, 23, "w4"], [36, 29, "w4"], [45, 21, "w3"]]) g.pixel(x, y, k);    g.pixel(30, 23, "r2"); g.pixel(41, 27, "v1");                             // a stray leaf, a fallen grape  });}// ---- toast: goblets lift, meet over the fruit, clink -------------------------------------------------------const CUP_L = { x0: 17, yb: 29 }, CUP_R = { x0: 41, yb: 29 };const LIFT = [[0, 0], [1, -2], [3, -5], [6, -8], [9, -10], [8, -11], [7, -10], [5, -8], [3, -5], [1, -2], [0, 0], [0, 0]];function cupShadow(g, c, dx, dy) {  if (dy === 0) return;  g.part("cupShadow", () => { for (let x = c.x0 + 1; x <= c.x0 + 3; x++) { g.pixel(x, c.yb, "w1"); if (dy > -6) g.pixel(x, c.yb - 1, chk(x, c.yb) ? "w1" : "w2"); } });}function clink(g, f) {  g.detached("clink", () => {    const cx = 31, cy = 10;    if (f === 4) for (const [x, y, k] of [[cx, cy - 1, "g3"], [cx, cy - 2, "n5"], [cx - 3, cy, "g2"], [cx + 3, cy, "g2"], [cx - 2, cy - 2, "g3"], [cx + 2, cy - 2, "g3"]]) g.pixel(x, y, k);    const drops = { 4: [[0, -1], [-1, -2], [1, -3]], 5: [[-2, -4], [2, -5], [0, -3]], 6: [[-3, -2], [3, -3]], 7: [[-4, 1], [4, 0]] }[f];    if (drops) drops.forEach(([dx, dy], i) => { g.pixel(cx + dx, cy + dy, i % 2 ? "r1" : "r2"); if (f <= 5) g.pixel(cx + dx, cy + dy + 1, "r0"); });  });}function bee(g, t) {  const x = Math.round(30 + 8 * Math.sin(TAU * t)), y = Math.round(9 + 2.5 * Math.sin(2 * TAU * t));  const dir = Math.cos(TAU * t) >= 0 ? 1 : -1;                              // faces where it is flying  const up = Math.round(t * N_TOAST * 2) % 2 === 0;  g.detached("bee", () => {    // striped body (tail, band, band, head), dark underside, flickering wings    g.pixel(x - 2 * dir, y, "ink"); g.pixel(x - dir, y, "g2"); g.pixel(x, y, "ink"); g.pixel(x + dir, y, "g3"); g.pixel(x + 2 * dir, y, "ink");    g.pixel(x - dir, y + 1, "g1"); g.pixel(x, y + 1, "ink"); g.pixel(x + dir, y + 1, "g1");    if (up) { g.pixel(x, y - 1, "n5"); g.pixel(x - dir, y - 2, "b3"); g.pixel(x, y - 2, "n5"); }    else { g.pixel(x - dir, y - 1, "n5"); g.pixel(x - 2 * dir, y - 1, "b3"); }  });}const FeastTable = sprite(pal, (g, t, mode) => {  t = ((t % 1) + 1) % 1;  const cleared = mode === "cleared", toast = mode === "toast";  contactShadow(g, CX, GROUND, 27, 2);  g.outlined("ink", () => trestles(g));  const f = flick(t, 0.2);  g.outlined("ink", () => tableTop(g, (cleared ? 5 : 7) + 0.9 * f.dh));  runner(g);  g.outlined("ink", () => garland(g));  if (cleared) crumbs(g);  // back row  let ft = 0, slosh = 0;  if (toast) { ft = Math.round(t * N_TOAST) % N_TOAST; slosh = ft >= 4 && ft <= 7 ? (ft % 2 ? 1 : 2) : 0; }  jug(g, slosh);  horn(g, !cleared);  cheese(g, !cleared);  candle(g, t, cleared);  // front row  bread(g, !cleared);  spill(g, !cleared);  pie(g, !cleared);  if (cleared) { tippedGoblet(g); goblet(g, "gobletR", CUP_R.x0, CUP_R.yb, 0, true); return; }  if (!toast) {    goblet(g, "gobletL", CUP_L.x0, CUP_L.yb, 0, false);    goblet(g, "gobletR", CUP_R.x0, CUP_R.yb, 0, false);    steam(g, t);    return;  }  const [dx, dy] = LIFT[ft];  cupShadow(g, CUP_L, dx, dy); cupShadow(g, CUP_R, -dx, dy);  steam(g, t);  const cs = ft >= 3 && ft <= 8 ? 1 + (ft % 2) : 0;  goblet(g, "gobletL", CUP_L.x0 + dx, CUP_L.yb + dy, cs, false);  goblet(g, "gobletR", CUP_R.x0 - dx, CUP_R.yb + dy, cs ? 3 - cs : 0, false);  clink(g, ft);  bee(g, t);});submit(FeastTable, {  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: "toast", args: ["toast"], animate: { fps: 10, duration: N_TOAST / 10, loop: true } },    { name: "cleared", args: ["cleared"], animate: { fps: 10, duration: N_CLEAR / 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.