Tessarune
Graveyard & Chapel · holy_font.js

Holy font

48×48 · 206 lines of JavaScript

A 48×48 floor prop: a carved stone basin with a broad lip, a moulded bowl and a sun boss on the front, on a fluted pedestal and a stepped plinth, filled with water. All three variants are 12-frame loops at 10 fps: “idle” has clear blue water with one slow ripple ring spreading per loop and glints twinkling; “glow” makes the water shine gold and cream, lighting the lip as motes of light rise; “cursed” turns it murky green, with bubbles that swell and pop and green slime running down the bowl.

Holy font — Rendered output6×
Rendered by this program, variant “glow” · 48×48 · 12 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.

holy_font.js · 1–7
// Holy font — 48x48 floor prop, top-down 3/4, key light top-left.// A carved stone basin (broad lip, moulded bowl, a sun boss on the front) on a fluted pedestal and a stepped// plinth, filled with water.// Variants (all 12f loops @ 10 fps)://   idle    — clear blue water, one slow ripple ring spreads per loop, glints twinkle//   glow    — the water shines gold/cream and lights the lip, motes of light rise (healing / save point)//   cursed  — murky green water, bubbles swell and pop, green slime runs down the bowl, the boss is dead

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

holy_font.js · 197–206
submit(Font, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N / 10, loop: true },  anchor: { x: CX, y: GROUND },  variants: [    { name: "idle", args: ["idle"] },    { name: "glow", args: ["glow"] },    { name: "cursed", args: ["cursed"] },  ],});
Show the whole program (206 lines)
holy_font.js · 206 lines
// Holy font — 48x48 floor prop, top-down 3/4, key light top-left.// A carved stone basin (broad lip, moulded bowl, a sun boss on the front) on a fluted pedestal and a stepped// plinth, filled with water.// Variants (all 12f loops @ 10 fps)://   idle    — clear blue water, one slow ripple ring spreads per loop, glints twinkle//   glow    — the water shines gold/cream and lights the lip, motes of light rise (healing / save point)//   cursed  — murky green water, bubbles swell and pop, green slime runs down the bowl, the boss is deadconst W = 48, H = 48, CX = 24, GROUND = 45;const N = 12;const LIP = { cy: 15.5, rx: 16, ry: 6 };const HOLE = { cy: 15, rx: 13, ry: 4 };const WATER = { cy: 15.6, rx: 12, ry: 3.2 };const WR = { idle: ["b0", "b1", "b2", "b3"], glow: ["g1", "g2", "g3", "n5"], cursed: ["e0", "e1", "e2", "e3"] };const stone = (u) => (u < -0.6 ? "n4" : u < -0.1 ? "n3" : u < 0.5 ? "n2" : "n1");// ---- plinth & pedestal ------------------------------------------------------------------------------------function plinth(g) {  g.part("plinth", () => {    g.field({ x: [10, 37], y: [38, 44] }, (x, y) => {      if ((x === 10 || x === 37) && y === 38) return null;           // clipped corners of the octagon      if (y === 38) return x < 30 ? "n4" : "n3";      if (y === 39) return x >= 26 ? (x > 33 ? "n1" : "n2") : "n3";   // the pedestal's shadow falls down-right      if (y === 40) return x < 28 ? "n4" : "n3";      if (y === 44) return "n1";      if (x === 10) return "n3";      if (y === 42 && (x - 12) % 4 === 0 && x < 36) return "n1";        // carved notches      return x > 31 ? "n1" : "n2";    });    g.pixel(35, 41, "n1"); g.pixel(34, 42, "n1");                       // a chip    g.pixel(11, 43, "e1"); g.pixel(12, 43, "e2"); g.pixel(11, 42, "e1"); // moss in the corner  });}function pedestal(g) {  const R = (y) => (y === 29 ? 5.5 : y >= 36 ? 6.5 : y === 35 ? 5.5 : 4.5);  g.part("pedestal", () => g.field({ x: [16, 32], y: [28, 38] }, (x, y) => {    const r = R(y), u = (x + 0.5 - CX) / r;    if (Math.abs(u) > 1 || y === 28) return null;    if (y === 38) return u < 0.3 ? "n1" : "n0";    if (y === 30) return u < 0.2 ? "n1" : "n0";                       // shadow under the collar    if (y === 29 || y >= 35) return stone(u - 0.1);    if ((x === 22 || x === 26) ) return x === 22 ? "n2" : "n0";       // flutes    return stone(u);  }));}// ---- basin ------------------------------------------------------------------------------------------------const bowlR = (y) => { const k = (y - 16) / 13.5; return k < 0 ? 16 : Math.max(5.5, 16 * Math.sqrt(Math.max(0, 1 - k * k))); };function bowl(g, s) {  g.part("bowl", () => g.field({ x: [7, 41], y: [16, 29] }, (x, y) => {    const r = bowlR(y), u = (x + 0.5 - CX) / r;    if (Math.abs(u) > 1) return null;    const nx = (x + 0.5 - CX) / LIP.rx, ny = (y + 0.5 - LIP.cy) / LIP.ry;    if (nx * nx + ny * ny <= 1) return null;                         // lip top drawn separately    if (s.slime && slimeAt(x, y)) return slimeAt(x, y);    const ey = y - (16 + 6 * Math.sqrt(Math.max(0, 1 - nx * nx)));    // rows below the lip's front edge    if (ey < 1.2) return stone(u - 0.25);                            // the lip's thick edge, lit    if (ey < 2.2) return u < 0.3 ? "n1" : "n0";                       // undercut beneath the lip    const li = litIndex(x, y, CX, 13, 16.5, 16, 4, 0.12 - Math.max(0, y - 25) * 0.07);    const base = ["n1", "n2", "n3", "n4"][Math.max(0, li)];    const dn = { n4: "n3", n3: "n2", n2: "n1", n1: "n0", n0: "n0" };    // a carved moulding round the belly: a groove with a lit bead beneath it    if (y === 24 && Math.abs(u) < 0.97) return dn[base];    if (y === 25 && Math.abs(u) < 0.97) return u < 0.35 ? { n1: "n2", n2: "n3", n3: "n4", n4: "n4" }[base] : base;    return base;  }));}function slimeAt(x, y) {  // green runs down the bowl from the lip (cursed)  const runs = [[14, 24], [19, 27], [31, 22]];  for (const [rx, end] of runs) {    if (x === rx && y <= end) return y === end ? "e3" : y === end - 1 ? "e2" : "e1";    if (x === rx + 1 && y >= end - 1 && y <= end) return "e1";    // the drop swells at the tip  }  return null;}function lip(g, s) {  const lit = s.state === "glow";  g.part("lip", () => g.field({ x: [CX - LIP.rx, CX + LIP.rx], y: [9, 22] }, (x, y) => {    const nx = (x + 0.5 - CX) / LIP.rx, ny = (y + 0.5 - LIP.cy) / LIP.ry;    if (nx * nx + ny * ny > 1) return null;    const hx = (x + 0.5 - CX) / HOLE.rx, hy = (y + 0.5 - HOLE.cy) / HOLE.ry;    if (hx * hx + hy * hy <= 1) return null;                          // the hollow, drawn by basin()    if (lit && hy > 0.2 && hx * hx + hy * hy < 1.25) return "n5";              // the water's light on the near inner rim    if (s.slime && hy < 0 && hx * hx + hy * hy < 1.3 && hash(x, y) > 0.55) return "e1";    const a = -0.7 * nx - 0.3 * ny;    return a > 0.45 ? "n4" : a > 0.0 ? "n3" : a > -0.45 ? "n2" : "n1";  }));}function basin(g, s, t) {  const R = WR[s.state];  g.part("water", () => g.field({ x: [CX - HOLE.rx, CX + HOLE.rx], y: [10, 20] }, (x, y) => {    const hx = (x + 0.5 - CX) / HOLE.rx, hy = (y + 0.5 - HOLE.cy) / HOLE.ry;    if (hx * hx + hy * hy > 1) return null;    const wx = (x + 0.5 - CX) / WATER.rx, wy = (y + 0.5 - WATER.cy) / WATER.ry, wr = Math.sqrt(wx * wx + wy * wy);    if (wr > 1) {                                                      // inner wall above the water; right side lit      if (s.state === "glow") return hx > 0.3 ? "n4" : "n3";      return hx > 0.35 ? "n2" : hx > -0.3 ? "n1" : "n0";    }    if (s.state === "cursed") {      // murk: slow-turning blotches of scum on dark water      const a = Math.atan2(wy, wx) - TAU * t;      const m = Math.sin(2 * a + wr * 5) + (wy > 0.55 ? 0.6 : 0);      if (wy < -0.5) return "e0";      return m > 1.0 ? "e2" : m > 0.1 ? "e1" : "e0";    }    // ripple ring spreading from the centre, one per loop    const p = 0.1 + 0.9 * t;    if (Math.abs(wr - p) < 0.12 && p < 0.95 && (p < 0.6 || wy > 0)) return s.state === "glow" ? "n5" : p < 0.35 ? R[3] : R[2];    if (s.state === "glow") {      if (wy < -0.45) return R[0];      return wr < 0.25 ? R[3] : wr < 0.6 ? R[2] : R[1];    }    if (wy < -0.45) return R[0];                                       // far edge in the lip's shadow    if (wy > 0.6 && Math.abs(wx) < 0.8) return R[2];                   // near meniscus    return R[1];  }));  // glints of the key light, twinkling in turn  if (s.state !== "cursed") g.detached("glints", () => {    const f = Math.round(t * N) % N;    const G = [[17, 14, 0], [29, 16, 4], [21, 17, 8]];    for (const [x, y, ph] of G) {      const k = (f - ph + N) % N;      if (k === 0 || k === 2) g.pixel(x, y, R[3] === "n5" ? "n5" : R[3]);      if (k === 1) { g.pixel(x, y, "n5"); g.pixel(x - 1, y, R[3]); g.pixel(x + 1, y, R[3]); }    }  });}function boss(g, s, t) {  // a small sun boss on the front of the bowl  const cx = CX, cy = 21;  const hot = s.state === "glow" ? (Math.cos(TAU * t) > 0 ? "g3" : "g2") : null;  g.part("boss", () => {    const dead = s.state === "cursed";    for (const [dx, dy] of [[0, -2], [0, 2], [-2, 0], [2, 0]]) g.pixel(cx + dx, cy + dy, dead ? "n1" : hot ? "g1" : "n1");    g.rect({ x: [cx - 1, cx], y: [cy - 1, cy] }, "n3", { fill: "n3" });    g.pixel(cx - 1, cy - 1, "n4"); g.pixel(cx, cy, "n1");    if (hot) { g.pixel(cx - 1, cy - 1, "g3"); g.pixel(cx, cy - 1, hot); g.pixel(cx - 1, cy, hot); g.pixel(cx, cy, "g1"); }    if (dead) { g.pixel(cx, cy - 1, "e1"); g.pixel(cx, cy, "e0"); }  });}// ---- effects ----------------------------------------------------------------------------------------------function motes(g, t) {  g.detached("motes", () => {    for (let i = 0; i < 5; i++) {      const p = (t + i / 5) % 1;      const x = CX + [-8, -3, 2, 6, 9][i] + Math.round(Math.sin(TAU * (p * 0.9 + i * 0.37)) * 1.5);      const y = 15 - Math.round(p * 15);      if (p < 0.3) { g.pixel(x, y, "n5"); g.pixel(x, y + 1, "g3"); g.pixel(x, y + 2, "g2"); }      else if (p < 0.6) { g.pixel(x, y, "g3"); g.pixel(x, y + 1, "g2"); }      else if (p < 0.85) g.pixel(x, y, "g2");      else if (chk(x, y)) g.pixel(x, y, "g1");    }    // a sparkle blinking over the water    const f = Math.round(t * N) % N;    const [sx, sy] = [[20, 11], [27, 9], [24, 12], [30, 11]][Math.floor(f / 3)];    if (f % 3 === 1) { g.pixel(sx, sy, "n5"); for (const [dx, dy] of [[0, -1], [0, 1], [-1, 0], [1, 0]]) g.pixel(sx + dx, sy + dy, "g3"); }    else if (f % 3 === 0) g.pixel(sx, sy, "g3");  });}function bubbles(g, t) {  const spots = [[-6, 0, 0.0], [3, 1, 0.33], [7, -1, 0.66], [-1, -1, 0.5]];  g.detached("bubbles", () => {    for (const [dx, dy, ph] of spots) {      const p = (t + ph) % 1, x = CX + dx, y = Math.round(WATER.cy) + dy;      if (p < 0.2) g.pixel(x, y, "e2");      else if (p < 0.45) { g.rect({ x: [x - 1, x], y: [y - 1, y] }, "e2", { fill: "e2" }); g.pixel(x - 1, y - 1, "e3"); }      else if (p < 0.6) { g.ellipse({ x: [x - 2, x + 1], y: [y - 2, y] }, "e1", { fill: "e2" }); g.pixel(x - 1, y - 2, "e3"); }      else if (p < 0.72) for (const [ox, oy] of [[-2, 0], [2, 0], [0, -2], [-1, 1]]) g.pixel(x + ox, y + oy, "e3");    }    // a sickly fume rising off the water    for (let i = 0; i < 3; i++) {      const p = (t + i / 3) % 1;      const x = CX + [-4, 1, 6][i] + Math.round(Math.sin(TAU * (p + i * 0.4)) * 2), y = 12 - Math.round(p * 11);      if (p < 0.4) g.pixel(x, y, "e2"); else if (p < 0.75 && chk(x, y)) g.pixel(x, y, "e1");    }  });}// ---- sprite -----------------------------------------------------------------------------------------------const Font = sprite(pal, (g, t, state) => {  const s = { state, slime: state === "cursed" };  contactShadow(g, CX, GROUND, 15, 2);  g.outlined("ink", () => { plinth(g); pedestal(g); bowl(g, s); lip(g, s); });  basin(g, s, t);  boss(g, s, t);  if (state === "glow") motes(g, t);  if (state === "cursed") bubbles(g, t);});submit(Font, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N / 10, loop: true },  anchor: { x: CX, y: GROUND },  variants: [    { name: "idle", args: ["idle"] },    { name: "glow", args: ["glow"] },    { name: "cursed", args: ["cursed"] },  ],});

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.