Tessarune
Graveyard & Chapel · open_grave.js

Open grave

64×48 · 244 lines of JavaScript

A 64×48 floor prop: a freshly dug rectangular pit with a trampled earth rim, a far wall of cut soil strata with roots and black depth, the spoil heap beside it with a spade stuck in its crown, and a small iron lantern hung on the spade’s grip. “idle” (12 frames) trickles loose soil off the heap into the pit while the lantern flame flickers warm; “dig” (10 frames, once) has the spade pull itself out, bite into the pit, fling a clod onto the heap and plant itself again, ending on the first frame of “idle”.

Open grave — Rendered output6×
Rendered by this program, variant “dig” · 64×48 · 10 frames
Graveyard & ChapelGet the animated sprites: Graveyard & Chapel on itch.ioVol.4 · 12 props · 33 animations

The header. The comment at the top of the file is the brief: size, view, light direction and every variant.

open_grave.js · 1–7
// Open grave — 64x48 floor prop, top-down 3/4, key light top-left.// A freshly dug rectangular pit (trampled earth rim, a far wall of cut soil strata with roots, black depth),// the spoil heap beside it with a spade stuck in its crown, and a small iron lantern hung on the spade's grip.// Variants://   idle  12f loop  loose soil trickles off the heap into the pit, the lantern flame flickers warm//   dig   10f once  the spade pulls itself out, bites into the pit, flings a clod onto the heap and plants//                   itself again; ends on idle frame 0

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

open_grave.js · 235–244
submit(OpenGrave, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.2, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle" },    { name: "dig", args: ["dig"], animate: { fps: 10, duration: 1.0, loop: false } },  ],});
Show the whole program (244 lines)
open_grave.js · 244 lines
// Open grave — 64x48 floor prop, top-down 3/4, key light top-left.// A freshly dug rectangular pit (trampled earth rim, a far wall of cut soil strata with roots, black depth),// the spoil heap beside it with a spade stuck in its crown, and a small iron lantern hung on the spade's grip.// Variants://   idle  12f loop  loose soil trickles off the heap into the pit, the lantern flame flickers warm//   dig   10f once  the spade pulls itself out, bites into the pit, flings a clod onto the heap and plants//                   itself again; ends on idle frame 0const W = 64, H = 48, CX = 32, GROUND = 45;const PIT = { x0: 5, x1: 31, y0: 20, y1: 36 };      // the openingconst WALL = 11;                                     // far wall height seen in the openingconst RIM = 2;                                       // trampled earth band around the opening// spoil heap: overlapping lumps [cx, cy, rx, ry, light bias]const LUMPS = [[47, 35, 12, 7, 0.12], [42, 30, 6, 4, 0.1], [49, 28, 6, 5, 0.1], [55, 32, 5, 4, 0], [39, 36, 5, 4, 0.05], [53, 38, 7, 4, 0]];const REST = { x: 48, y: 29, a: -23 };               // spade: blade tip position and handle angle (deg) when plantedconst DIRT = ["w0", "w1", "w2", "w3"];const inOpen = (x, y) => x >= PIT.x0 && x <= PIT.x1 && y >= PIT.y0 && y <= PIT.y1;const inRimBand = (x, y) => {  if (inOpen(x, y)) return false;  const dx = Math.max(PIT.x0 - RIM - x, x - PIT.x1 - RIM, 0), dy = Math.max(PIT.y0 - RIM - y, y - PIT.y1 - RIM - 1, 0);  if (dx > 0 || dy > 0) return false;  // clip the corners so the band reads as dug earth, not a picture frame  const cx = x < PIT.x0 ? PIT.x0 - x : x > PIT.x1 ? x - PIT.x1 : 0, cy = y < PIT.y0 ? PIT.y0 - y : y > PIT.y1 ? y - PIT.y1 : 0;  if (cx + cy > RIM + 1) return false;  // a ragged outer edge: some columns / rows lose their outermost pixel  if ((y === PIT.y0 - RIM || y === PIT.y1 + RIM + 1) && Math.sin(x * 1.7) > 0.55) return false;  if ((x === PIT.x0 - RIM || x === PIT.x1 + RIM) && Math.sin(y * 2.1) > 0.6) return false;  return true;};// ---- pit ------------------------------------------------------------------------------------------------------function pitTone(x, y) {  const d = y - PIT.y0;                                // depth down the far wall  const sh = x - PIT.x0 < 2 + d * 0.45 ? 1 : 0;        // the left rim throws a shadow down and to the right  if (d < WALL) {    // cut soil strata with wavy seams: dark topsoil, a paler clay band, then earth fading into the dark    const b = d + Math.round(Math.sin(x * 0.7) * 0.6 + Math.sin(x * 0.23 + 1) * 0.8);    let i = b < 2 ? 1 : b < 4 ? 2 : b < 6 ? 1 : b < 8 ? 0 : -1;    i -= sh;    if (i >= 0) return DIRT[i];    return d >= WALL - 2 ? "ink" : "n0";  }  return "ink";                                        // the bottom of the grave}function rimTone(x, y) {  const inner = inOpen(x - 1, y) || inOpen(x + 1, y) || inOpen(x, y - 1) || inOpen(x, y + 1);  const near = y > PIT.y1, far = y < PIT.y0, left = x < PIT.x0;  if (inner && far) return "w3";                       // the far lip catches the light  if (inner && left) return "w3";  if (inner && near) return "w1";                      // near lip: we look over its dark edge  if (far || left) return "w2";  if (near && y === PIT.y1 + RIM + 1) return "w0";  return "w1";}function pit(g) {  g.part("rim", () => g.field({ x: [PIT.x0 - RIM - 1, PIT.x1 + RIM + 1], y: [PIT.y0 - RIM - 1, PIT.y1 + RIM + 1] }, (x, y) =>    inRimBand(x, y) ? rimTone(x, y) : null));  g.part("pit", () => g.field({ x: [PIT.x0, PIT.x1], y: [PIT.y0, PIT.y1] }, (x, y) => pitTone(x, y)));}function pitDetail(g) {  g.part("roots", () => {    // pale roots hanging out of the far wall, stones in the strata, clods on the bottom    for (const [x, y, n] of [[12, 21, 3], [21, 21, 2], [27, 22, 3]]) for (let i = 0; i < n; i++) g.pixel(x + (i === n - 1 ? 1 : 0), y + i, i === 0 ? "w4" : "w3");    for (const [x, y] of [[16, 23], [24, 24], [29, 26]]) { g.pixel(x, y, "n3"); g.pixel(x + 1, y, "n1"); }    for (const [x, y] of [[14, 34], [22, 33], [27, 35]]) g.pixel(x, y, "n0");    // vertical bites of the spade in the far wall: a shadowed stroke with a lit edge beside it    for (const x of [10, 18, 25]) { g.line([x, PIT.y0 + 3], [x, PIT.y0 + 6], "w0"); g.pixel(x + 1, PIT.y0 + 3, "w3"); }  });  g.part("turf", () => {    // torn grass on the outer edge of the rim    for (const [x, y, k] of [[4, 18, "e1"], [5, 17, "e2"], [9, 17, "e1"], [10, 17, "e2"], [18, 17, "e1"], [25, 17, "e1"], [26, 17, "e2"],      [2, 25, "e1"], [2, 26, "e0"], [2, 32, "e1"], [8, 39, "e0"], [9, 39, "e1"], [20, 39, "e0"], [29, 39, "e1"]]) g.pixel(x, y, k);  });}// ---- spoil heap -----------------------------------------------------------------------------------------------function heap(g) {  g.part("heap", () => g.field({ x: [26, 62], y: [20, 43] }, (x, y) => {    // a spoil heap built from overlapping lumps, each lit as an ellipsoid; clods and stones added as marks    let i = -1;    for (const [cx, cy, rx, ry, b] of LUMPS) i = Math.max(i, litIndex(x, y, cx, cy, rx, ry, 4, b));    if (i < 0) return null;    return DIRT[i];  }));  g.part("clods", () => {    for (const [x, y] of [[41, 29], [53, 27], [44, 37], [56, 34], [49, 40], [37, 36]]) { g.pixel(x, y, "w3"); g.pixel(x + 1, y, "w1"); g.pixel(x, y + 1, "w1"); }    for (const [x, y] of [[46, 33], [58, 31], [41, 40]]) { g.pixel(x, y, "n3"); g.pixel(x + 1, y, "n1"); }    g.pixel(51, 38, "w0"); g.pixel(52, 38, "w0");  });}function hole(g, open) {  // the slot the spade leaves in the crown when it is pulled out  if (!open) return;  g.part("slot", () => { g.line([REST.x - 2, REST.y - 2], [REST.x + 2, REST.y - 2], "w0"); g.line([REST.x - 1, REST.y - 1], [REST.x + 1, REST.y - 1], "ink"); g.pixel(REST.x - 2, REST.y - 1, "w3"); });}function trickle(g, t) {  // three grains roll down the heap's left flank and drop into the pit  const P = [[39, 30], [37, 31], [36, 32], [35, 33], [33, 33], [32, 34], [31, 35]];  g.detached("trickle", () => {    for (let k = 0; k < 3; k++) {      const p = (t + k / 3) % 1;      if (p > 0.85) continue;      const s = p / 0.85 * (P.length - 1), i = Math.floor(s);      const [x, y] = P[i];      g.pixel(x, y, i < 3 ? "w3" : "w2");      if (i < 2 && k === 0) g.pixel(x + 1, y, "w1");    }  });}// ---- spade and lantern ----------------------------------------------------------------------------------------------const rot = (px, py, a) => { const c = Math.cos(a * Math.PI / 180), s = Math.sin(a * Math.PI / 180); return [px * c - py * s, px * s + py * c]; };function spadeLocal(g, loaded) {  // local frame: blade tip at the origin, handle up the -y axis  g.part("blade", () => {    g.fillPoly([[-2, -7], [2, -7], [2, -2], [1, 0], [-1, 0], [-2, -2]], "n2");    g.line([-2, -7], [-2, -2], "n4");                   // lit left edge    g.line([2, -7], [2, -2], "n1");    g.pixel(-1, -6, "n3"); g.pixel(1, -1, "n1");    g.line([-1, -8], [1, -8], "n1"); g.pixel(-1, -8, "n3");   // socket    if (loaded) { g.rect({ x: [-2, 2], y: [-5, -3] }, "w2", { fill: "w2" }); g.pixel(-1, -5, "w3"); g.pixel(2, -3, "w0"); g.pixel(1, -6, "w1"); }  });  g.part("shaft", () => {    g.line([0, -9], [0, -21], "w3");    g.line([1, -9], [1, -21], "w1");    g.pixel(0, -14, "w4"); g.pixel(1, -17, "w0");    // T-grip: a short crossbar, lit on its left end    g.line([0, -22], [1, -22], "w2");    g.rect({ x: [-2, 3], y: [-24, -23] }, "w2", { fill: "w2" });    g.line([-2, -24], [1, -24], "w3"); g.pixel(-2, -23, "w3"); g.line([2, -23], [3, -23], "w1");  });}function lanternAt(g, lx, ly, sw, t) {  // hook, cap, glass with flame, base; sw = swing offset of the body (px)  const flick = [0, 1, 0, 2, 1, 0, 1, 2, 0, 1, 0, 1][Math.floor(((t % 1) + 1) % 1 * 12) % 12];  const bx = lx + sw;  g.outlined("ink", () => g.part("lantern", () => {    g.line([lx, ly], [lx, ly + 1], "n2");    g.pixel(bx, ly + 2, "n3");    g.line([bx - 1, ly + 3], [bx + 1, ly + 3], "n3"); g.pixel(bx + 1, ly + 3, "n1");    g.line([bx - 2, ly + 4], [bx + 2, ly + 4], "n2"); g.pixel(bx - 2, ly + 4, "n4");    g.rect({ x: [bx - 1, bx + 1], y: [ly + 5, ly + 8] }, "r3", { fill: "r3" });    g.line([bx - 2, ly + 5], [bx - 2, ly + 8], "n2"); g.line([bx + 2, ly + 5], [bx + 2, ly + 8], "n1");    g.pixel(bx, ly + 6, "g3"); g.pixel(bx, ly + 7, flick === 2 ? "g3" : "g2");    if (flick === 1) g.pixel(bx, ly + 5, "g2");    if (flick === 2) g.pixel(bx - 1, ly + 8, "r2");    g.pixel(bx - 1, ly + 5, "g3");                       // glint on the glass    g.line([bx - 2, ly + 9], [bx + 2, ly + 9], "n1"); g.pixel(bx - 2, ly + 9, "n3");  }));  g.detached("glow", () => {    // a few warm sparks of halo beside the glass, breathing with the flicker    const k = flick === 0 ? "r1" : "r2";    g.pixel(bx - 4, ly + 6, k); g.pixel(bx + 4, ly + 7, k);    if (flick === 2) { g.pixel(bx - 4, ly + 8, "r1"); g.pixel(bx + 4, ly + 5, "r1"); }  });}function spade(g, S, t) {  const { x, y, a } = S;  g.outlined("ink", () => g.at(x, y, () => g.rotate(a, () => spadeLocal(g, S.loaded))));  const [hx, hy] = rot(-2, -22, a);  lanternAt(g, Math.round(x + hx), Math.round(y + hy) + 1, S.sw, t);}function collar(g) {  // a little cone of soil heaped round the planted blade (covers its lower half)  g.part("collar", () => g.field({ x: [REST.x - 5, REST.x + 5], y: [REST.y - 4, REST.y + 2] }, (x, y) => {    const i = litIndex(x, y, REST.x + 0.5, REST.y - 0.5, 4.5, 3, 4, 0.15);    return i < 0 ? null : DIRT[i];  }));}// ---- dig -------------------------------------------------------------------------------------------------------------const DIG = 10;// per frame: blade tip x, y, handle angle, loaded, lantern swingconst KEYS = [  null,  [47, 25, -25, 0, 1],     // pulls itself out  [40, 29, -45, 0, 2],     // tips over the pit  [31, 32, -58, 0, 1],     // bites into the pit  [33, 27, -50, 1, -1],    // lifts a load  [42, 23, -32, 1, -2],    // swings back towards the heap  [51, 22, -42, 0, -1],    // flings (the clod leaves the blade)  [50, 26, -30, 0, 1],     // comes down  [48, 28, -24, 0, 0],     // plants itself  null,];function stateAt(state, t) {  t = ((t % 1) + 1) % 1;  if (state === "dig") {    const f = Math.min(DIG - 1, Math.round(t * DIG));    if (f === 0 || f === DIG - 1) return stateAt("idle", 0);    const [x, y, a, loaded, sw] = KEYS[f];    return { x, y, a, loaded: !!loaded, sw, f, t: f / DIG, planted: f === 8 };  }  return { x: REST.x, y: REST.y, a: REST.a, loaded: false, sw: 0, f: -1, t, planted: true };}function flung(g, f) {  // the clod arcs onto the heap's crown and breaks  const path = { 6: [[54, 17, 3]], 7: [[56, 21, 3]], 8: [[55, 27, 2], [58, 29, 1], [52, 28, 1]] }[f];  if (!path) return;  g.outlined("ink", () => g.part("clod", () => {    for (const [x, y, s] of path) {      if (s >= 2) { g.rect({ x: [x, x + s - 1], y: [y, y + 1] }, "w1", { fill: "w1" }); g.pixel(x, y, "w3"); g.pixel(x + s - 1, y + 1, "w0"); }      else g.pixel(x, y, "w2");    }  }));  if (f === 8) g.detached("puff", () => { for (const [x, y] of [[53, 25], [58, 26], [60, 28], [51, 28]]) g.pixel(x, y, "n3"); });}function pitSpray(g, f) {  // the blade's bite throws a few crumbs inside the pit  if (f !== 3 && f !== 4) return;  g.detached("spray", () => {    const pts = f === 3 ? [[27, 28], [24, 27], [29, 26]] : [[26, 25], [23, 23], [30, 22]];    for (const [x, y] of pts) g.pixel(x, y, "w2");  });}const OpenGrave = sprite(pal, (g, t, state) => {  const S = stateAt(state, t);  const f = S.f;  contactShadow(g, CX, GROUND - 1, 30, 2);  g.outlined("ink", () => { pit(g); heap(g); });  pitDetail(g);  hole(g, !S.planted);  spade(g, S, S.t);  if (S.planted) collar(g);                  // soil over the buried half of the blade  trickle(g, f < 0 ? S.t : 0);  if (f > 0) { flung(g, f); pitSpray(g, f); }  if (S.planted) g.part("lampLight", () => {    // the lantern's warm light on the heap's shoulder below it    const on = Math.floor(S.t * 12) % 3 !== 1;    for (const [x, y] of [[40, 27], [42, 26], [38, 29]]) g.pixel(x, y, on ? "w4" : "w3");  });});submit(OpenGrave, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.2, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle" },    { name: "dig", args: ["dig"], animate: { fps: 10, duration: 1.0, loop: false } },  ],});

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.