Tessarune
Graveyard & Chapel · coffin.js

Coffin

48×48 · 410 lines of JavaScript

A 48×48 floor prop: a six-sided coffin lying lengthwise, modelled as convex solids and ray-cast per pixel, with iron bar handles, a brass cross with a violet stone inlaid in the lid, and dark velvet lining with button tufts and a pale pillow inside. The lid is a rigid body. “closed” (12 frames) is still, then two knocks from inside jolt the lid up with puffs of dust; “open” (10 frames, once) slides the lid right until it tips off and lands against the wall; “open-idle” (12 frames) breathes a violet glow out of the lining as motes rise.

Coffin — Rendered output6×
Rendered by this program, variant “open” · 48×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.

coffin.js · 1–10
// Coffin — 48x48 floor prop, top-down 3/4, key light top-left. Lies lengthwise, head end away from the camera.// A classic six-sided (toe-pincher) coffin modelled as convex solids and ray-cast per pixel along the oblique view// ray (screen y = BASE - z - K*depth): a hollow plank box with a base trim, a bevelled lid slab one step larger than// the box, iron bar handles on the long walls and a ring pull on the foot end, a brass cross with a violet stone// inlaid in the lid, and inside: dark velvet lining (r0 / v0), button tufts and a pale pillow.// The lid is a rigid body: it slides across the rim, tips over the right wall and comes to rest leaning against it.// Variants://   closed     loop 12f   — still, then two knocks from inside: the lid jolts up (the 2nd time askew), dust puffs//   open       once 10f   — a thump, the lid slides right, tips off and lands against the wall; ends on open-idle f0//   open-idle  loop 12f   — lid leaning on the floor, violet glow breathing out of the lining, motes rising

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

coffin.js · 401–410
submit(Coffin, {  size: [48, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 23, y: GROUND },  variants: [    { name: "closed", args: ["closed"] },    { name: "open", args: ["open"], animate: { fps: 10, duration: N_OPEN / 10, loop: false } },    { name: "open-idle", args: ["open-idle"] },  ],});
Show the whole program (410 lines)
coffin.js · 410 lines
// Coffin — 48x48 floor prop, top-down 3/4, key light top-left. Lies lengthwise, head end away from the camera.// A classic six-sided (toe-pincher) coffin modelled as convex solids and ray-cast per pixel along the oblique view// ray (screen y = BASE - z - K*depth): a hollow plank box with a base trim, a bevelled lid slab one step larger than// the box, iron bar handles on the long walls and a ring pull on the foot end, a brass cross with a violet stone// inlaid in the lid, and inside: dark velvet lining (r0 / v0), button tufts and a pale pillow.// The lid is a rigid body: it slides across the rim, tips over the right wall and comes to rest leaning against it.// Variants://   closed     loop 12f   — still, then two knocks from inside: the lid jolts up (the 2nd time askew), dust puffs//   open       once 10f   — a thump, the lid slides right, tips off and lands against the wall; ends on open-idle f0//   open-idle  loop 12f   — lid leaning on the floor, violet glow breathing out of the lining, motes risingconst CX = 22, GROUND = 44, K = 0.6;const BASE = GROUND - K * 18;                      // foot end (y = -18) sits on the ground lineconst HB = 7, LT = 2, T = 1.1, FLOOR = 1.4, RIM = 7.0;const N_IDLE = 12, N_OPEN = 10;const LV = norm3([-0.6, -0.25, 0.76]);             // toward the key light (x right, y away, z up)const WOOD = ["w0", "w1", "w2", "w3"], IRON = ["n0", "n1", "n2", "n3"], GOLD = ["g0", "g1", "g2", "g3"];function norm3(v) { const m = Math.hypot(v[0], v[1], v[2]); return [v[0] / m, v[1] / m, v[2] / m]; }const dotL = (n) => n[0] * LV[0] + n[1] * LV[1] + n[2] * LV[2];// ---- the hexagon (x across, y along; head at +y), counter-clockwiseconst hexPts = (hw, hs, hf, yh, ys, yf) => [[hf, yf], [hs, ys], [hw, yh], [-hw, yh], [-hs, ys], [-hf, yf]];const BOXHEX = hexPts(5.0, 8.0, 4.0, 18, 9, -18);const CAVHEX = hexPts(5.0 - T, 8.0 - T, 4.0 - T, 18 - T, 9, -18 + T);const LIDHEX = hexPts(5.8, 8.8, 4.8, 18.8, 9, -18.8);// half-width of the box's lower (shoulder -> foot) walls at yconst hwLow = (y) => 4.0 + ((y + 18) / 27) * 4;// convex prism as half-spaces [nx, ny, nz, d] (inside: n.p <= d); faces 0..5 = hex edges, 6 = bottom, 7 = topfunction prism(poly, z0, z1) {  const pl = [];  for (let i = 0; i < poly.length; i++) {    const a = poly[i], b = poly[(i + 1) % poly.length];    const ex = b[0] - a[0], ey = b[1] - a[1], m = Math.hypot(ex, ey);    const n = [ey / m, -ex / m, 0];    pl.push([n[0], n[1], 0, n[0] * a[0] + n[1] * a[1]]);  }  pl.push([0, 0, -1, -z0], [0, 0, 1, z1]);  return pl;}const boxPl = (x0, x1, y0, y1, z0, z1) => [[1, 0, 0, x1], [-1, 0, 0, -x0], [0, 1, 0, y1], [0, -1, 0, -y0], [0, 0, -1, -z0], [0, 0, 1, z1]];const BOX = prism(BOXHEX, 0, HB);const CAV = prism(CAVHEX, FLOOR, 99);const LID = prism(LIDHEX, 0, LT);const HANDLES = [];for (const s of [-1, 1]) for (const y of [-10, 2]) {  const w = hwLow(y);  HANDLES.push(boxPl(s < 0 ? -w - 1.3 : w - 0.2, s < 0 ? -w + 0.2 : w + 1.3, y - 2.2, y + 2.2, 2.9, 4.3));}const PILLOW = { c: [0, 13.2, FLOOR + 1.2], r: [3.2, 2.4, 1.5] };let TIN = 0, TOUT = 0, CIN = -1, COUT = -1;function convex(o, d, planes) {  let tin = -1e9, tout = 1e9, cin = -1, cout = -1;  for (let i = 0; i < planes.length; i++) {    const p = planes[i];    const den = p[0] * d[0] + p[1] * d[1] + p[2] * d[2];    const num = p[3] - (p[0] * o[0] + p[1] * o[1] + p[2] * o[2]);    if (den > -1e-9 && den < 1e-9) { if (num < 0) return false; continue; }    const t = num / den;    if (den < 0) { if (t > tin) { tin = t; cin = i; } } else if (t < tout) { tout = t; cout = i; }    if (tin > tout) return false;  }  TIN = tin; TOUT = tout; CIN = cin; COUT = cout;  return true;}function ellipsoid(o, d, e) {  const [cx, cy, cz] = e.c, [rx, ry, rz] = e.r;  const ox = (o[0] - cx) / rx, oy = (o[1] - cy) / ry, oz = (o[2] - cz) / rz;  const dx = d[0] / rx, dy = d[1] / ry, dz = d[2] / rz;  const A = dx * dx + dy * dy + dz * dz, B = 2 * (ox * dx + oy * dy + oz * dz), C = ox * ox + oy * oy + oz * oz - 1;  const disc = B * B - 4 * A * C;  if (disc < 0) return -1;  return (-B - Math.sqrt(disc)) / (2 * A);}// ---- lid pose: yaw about its centre, then pivot on its underside at local x = xc, which sits on the rim point// (RIM, ty, HB + lift); phi tips the right side down.function lidXform(ps) {  const cy = Math.cos(ps.yaw), sy = Math.sin(ps.yaw), cp = Math.cos(ps.phi), sp = Math.sin(ps.phi);  const rotN = (n) => { // local -> world (no translation)    const x1 = n[0] * cy - n[1] * sy, y1 = n[0] * sy + n[1] * cy, z1 = n[2];    return [x1 * cp + z1 * sp, y1, -x1 * sp + z1 * cp];  };  const invN = (v) => { // world -> local (no translation)    const x1 = v[0] * cp - v[2] * sp, z1 = v[0] * sp + v[2] * cp, y1 = v[1];    return [x1 * cy + y1 * sy, -x1 * sy + y1 * cy, z1];  };  const P = [RIM, ps.ty, HB + ps.lift];  const toL = (p) => { const q = invN([p[0] - P[0], p[1] - P[1], p[2] - P[2]]); q[0] += ps.xc * cy; q[1] += ps.xc * sy; return q; };  // (the pivot offset is applied before the yaw, so move it through the yaw too)  const toW = (q) => { const r = rotN([q[0] - ps.xc * cy, q[1] - ps.xc * sy, q[2]]); return [r[0] + P[0], r[1] + P[1], r[2] + P[2]]; };  return { rotN, invN, toL, toW };}const project = (p, dx) => [CX + p[0] - 0.5 + (dx || 0), BASE - p[2] - K * p[1] - 0.5, p[1] + 40];// ---- one frame: cast every pixelfunction castCoffin(ps, dx) {  const X = lidXform(ps);  const d = [0, 1, -K], dl = X.invN(d);  const buf = new Array(48 * 48).fill(null);  for (let py = 0; py < 48; py++) for (let px = 0; px < 48; px++) {    const o = [px + 0.5 - CX - dx, -40, BASE - (py + 0.5) + K * 40];    let bt = 1e9, hit = null;    if (convex(o, d, BOX)) {      const tb = TIN, cb = CIN, qx = o[0], qy = o[1] + tb;      let inside = false;      if (cb === 7 && convex([qx, qy, HB], [0, 0, -1], CAV.slice(0, 6))) inside = true;      if (inside && convex(o, d, CAV)) {        bt = TOUT; hit = { id: "cav", code: COUT };        const te = ellipsoid(o, d, PILLOW);        if (te > 0 && te < bt) { bt = te; hit = { id: "pillow" }; }      } else { bt = tb; hit = { id: "box", code: cb }; }    }    for (let i = 0; i < HANDLES.length; i++) if (convex(o, d, HANDLES[i]) && TIN < bt) { bt = TIN; hit = { id: "handle", code: CIN, i }; }    const ol = X.toL(o);    if (convex(ol, dl, LID) && TIN < bt) { bt = TIN; hit = { id: "lid", code: CIN }; }    if (!hit) continue;    const p = hit.id === "lid" ? [ol[0] + bt * dl[0], ol[1] + bt * dl[1], ol[2] + bt * dl[2]] : [o[0], o[1] + bt, o[2] - K * bt];    const c = classify(hit, p, X);    c.t = bt;    buf[py * 48 + px] = c;  }  return buf;}function planeN(pl, code) { const p = pl[code]; return [p[0], p[1], p[2]]; }// distance from (x, y) to the lid hexagon's edges: [distance, outward normal of the nearest edge]function lidEdge(x, y) {  let best = 1e9, bn = null;  for (let i = 0; i < 6; i++) {    const p = LID[i], dd = p[3] - (p[0] * x + p[1] * y);    if (dd < best) { best = dd; bn = [p[0], p[1]]; }  }  return [best, bn];}function classify(hit, p, X) {  const [x, y, z] = p;  if (hit.id === "box") {    const n = planeN(BOX, hit.code);    if (hit.code === 7) return { part: "box", mat: "wood", s: "rim", d: 0.95 };    const foot = hit.code === 5;    if (foot) {      // ring pull on the foot end: a small plate and a hanging ring      if (Math.abs(x) < 1.1 && Math.abs(z - 5.2) < 0.7) return { part: "handles", mat: "iron", d: dotL(n), plate: true };      const r = Math.hypot(x, (z - 3.4) * 1.1);      if (r > 0.9 && r < 1.9) return { part: "handles", mat: "iron", d: dotL(n), ring: true, top: z > 3.4 };    }    return { part: "box", mat: "wood", s: "wall" + hit.code, d: dotL(n), z, u: foot ? x : y };  }  if (hit.id === "cav") {    if (hit.code === 6) return { part: "lining", mat: "velvet", s: "floor", x, y };    const n = planeN(CAV, hit.code);    return { part: "lining", mat: "velvet", s: "wall", d: dotL([-n[0], -n[1], 0.2]), z };  }  if (hit.id === "pillow") {    const c = PILLOW.c, r = PILLOW.r;    const n = norm3([(x - c[0]) / (r[0] * r[0]), (y - c[1]) / (r[1] * r[1]), (z - c[2]) / (r[2] * r[2])]);    return { part: "pillow", mat: "pillow", d: dotL(n), seam: Math.abs(y - c[1]) < 0.35 };  }  if (hit.id === "handle") {    const pl = HANDLES[hit.i], n = planeN(pl, hit.code);    return { part: "handles", mat: "iron", d: dotL(n), bar: true };  }  // lid: local coordinates  let n = planeN(LID, hit.code);  if (hit.code === 7) {    const [e, en] = lidEdge(x, y);    // brass cross + violet stone (inlaid, flush)    const vb = Math.abs(x) < 1.05 && y > -3 && y < 14.5, hb = Math.abs(y - 9) < 1.2 && Math.abs(x) < 4.2;    if (Math.abs(x) < 1.05 && Math.abs(y - 9) < 1.2) return { part: "gem", mat: "gem", lx: x, ly: y };    if (vb || hb) {      const edgeL = vb && hb ? 0 : vb ? (x < -0.45 ? 1 : x > 0.45 ? -1 : 0) : 0;      const edgeT = hb && !vb ? (y > 9.6 ? 1 : y < 8.4 ? -1 : 0) : vb && !hb ? (y > 13.7 ? 1 : y < -2.2 ? -1 : 0) : 0;      const tipL = hb && !vb && x < -3.5, tipR = hb && !vb && x > 3.5;      return { part: "cross", mat: "brass", edge: edgeL + edgeT + (tipL ? 1 : 0) - (tipR ? 1 : 0), d: dotL(X.rotN([0, 0, 1])) };    }    if (e < 1.05) n = norm3([en[0] * 0.8, en[1] * 0.8, 0.6]);     // bevel    const groove = e > 2.3 && e < 2.95;    const grain = (Math.abs(x + 3.2) < 0.35 && hash(1, Math.floor(y / 5)) > 0.35) || (Math.abs(x - 2.8) < 0.35 && hash(2, Math.floor((y + 2) / 6)) > 0.45);    return { part: "lid", mat: "wood", s: "lidtop", d: dotL(X.rotN(n)), bevel: e < 1.05, groove, grain: grain && e >= 2.95, lid: true };  }  return { part: "lid", mat: "wood", s: "lidside" + hit.code, d: dotL(X.rotN(n)), edgeTop: z > LT - 0.7, lid: true };}function level(d) { return d > 0.8 ? 3 : d > 0.45 ? 2 : d > 0.08 ? 1 : 0; }function tone(buf, fx) {  const keys = new Array(48 * 48).fill(null);  for (let i = 0; i < 48 * 48; i++) {    const b = buf[i]; if (!b) continue;    const px = i % 48, py = (i - px) / 48;    const up = py > 0 ? buf[i - 48] : null, dn = py < 47 ? buf[i + 48] : null, lf = px > 0 ? buf[i - 1] : null;    let key;    if (b.mat === "wood") {      let lv = level(b.d);      if (b.s === "rim") lv = !lf || lf.part !== "box" || !up || up.part !== "box" ? 3 : 2;      else if (b.s === "lidtop") {        lv = b.bevel ? level(b.d) : b.groove ? 0 : 1;        if (b.grain) lv = 0;        else if (!b.bevel && !b.groove && lf && lf.groove) lv = 2;   // far wall of the groove catches light      } else if (b.s.startsWith("lidside")) {        lv = Math.max(0, level(b.d) - 1) + (b.edgeTop ? 1 : 0);      } else {        // box walls: base trim, one plank seam, the lid's overhang shades the top row        lv = Math.max(0, lv - 1);        if (b.z < 1.2) lv = Math.max(0, lv - 1);        else if (b.z < 1.9) lv = Math.min(3, lv + 1);        else if (Math.abs(b.z - 4.4) < 0.5) lv = Math.max(0, lv - 1);        if (up && up.lid) lv = 0;      }      key = WOOD[Math.max(0, Math.min(3, lv))];    } else if (b.mat === "iron") {      let lv = level(b.d);      const same = (q) => q && q.part === "handles";      if (b.bar) lv = !same(up) ? (b.d > 0 ? 3 : 2) : !same(dn) ? 0 : 1;      if (b.plate) lv = 2;      if (b.ring) lv = b.top ? 3 : 1;      key = IRON[Math.max(0, Math.min(3, lv))];    } else if (b.mat === "brass") {      let lv = 2 + b.edge;      if (fx.glint) lv = Math.min(3, lv + 1);      key = GOLD[Math.max(0, Math.min(3, lv - (b.d < 0.6 ? 1 : 0)))];    } else if (b.mat === "gem") {      const hi = b.lx < 0 && b.ly > 9;      const p = fx.gem || 0;      key = hi ? (p > 0.5 ? "n5" : "v3") : b.lx > 0.2 && b.ly < 9 ? (p > 0.5 ? "v2" : "v0") : p > 0.5 ? "v3" : "v1";    } else if (b.mat === "velvet") {      if (b.s === "wall") key = up && up.part === "box" ? "v0" : b.d > 0.3 ? "r0" : "v0";      else {        // floor: the left wall throws a shadow; button tufts on a diamond lattice        const gap = b.x + hwLow(Math.min(b.y, 10)) - T;        const u = (b.x + 0.5) / 3.2 + b.y / 5.2, v = (b.x + 0.5) / 3.2 - b.y / 5.2;        const btn = Math.abs(u - Math.round(u)) < 0.16 && Math.abs(v - Math.round(v)) < 0.16;        key = gap < 1.6 ? "v0" : btn ? "r1" : "r0";        const gl = fx.glow && gap >= 1.6 ? fx.glow(b.x, b.y, px, py) : 0;        if (gl === 2) key = btn ? "v3" : "v2"; else if (gl === 1) key = "v1";      }    } else if (b.mat === "pillow") {      key = b.seam ? "n1" : b.d > 0.6 ? "n4" : b.d > 0.15 ? "n3" : "n2";    }    keys[i] = key;  }  return keys;}const PARTS = ["box", "lining", "pillow", "handles", "lid", "cross", "gem"];function drawCoffin(g, ps, fx, dx) {  const buf = castCoffin(ps, dx || 0);  const keys = tone(buf, fx);  const seen = new Set();  for (let i = 0; i < 48 * 48; i++) if (buf[i]) seen.add(buf[i].part);  g.outlined("ink", () => {    for (const part of PARTS) if (seen.has(part)) g.part(part, () => g.field({ x: [0, 47], y: [0, 47] }, (x, y) => {      const b = buf[y * 48 + x];      return b && b.part === part ? keys[y * 48 + x] : null;    }));  });  return buf;}// ---- effectsfunction visible(buf, x, y, depth, slack) {  if (x < 0 || y < 0 || x > 47 || y > 47) return false;  const b = buf[y * 48 + x];  return !b || depth < b.t + slack;}// a dust puff: p 0..1 — a tight grey burst, a looser cloud, then a few thinning specks; dir drifts it sidewaysconst PUFF = [  [[0, 0, "n4"], [1, 0, "n4"], [2, 0, "n3"], [1, -1, "n5"]],  [[1, -2, "n4"], [0, -1, "n4"], [2, -1, "n5"], [3, -1, "n4"], [1, 0, "n3"], [3, 0, "n3"], [4, 0, "n3"]],  [[1, -3, "n3"], [3, -2, "n4"], [0, -1, "n3"], [4, -1, "n3"], [2, 0, "n3"]],  [[2, -4, "n3"], [5, -2, "n3"], [0, -2, "n3"]],];function puff(g, x, y, p, dir) {  if (p < 0 || p >= 1) return;  const st = Math.min(3, Math.floor(p * 4));  const ox = x + Math.round(dir * p * 2), oy = y - Math.round(p * 1.5);  for (const [a, b, k] of PUFF[st]) g.pixel(ox + a * dir, oy + b, k);}// the lining's breath: violet wisps curling up out of the opening, motes rising (cavity-floor glow is in tone())const WISPS = [[-2, -3, 0.0], [2, 3, 0.33], [0, -10, 0.67]];const MOTES = [[-3, 5, 0.1], [3, -6, 0.45], [1, -13, 0.8], [-1, 9, 0.62]];function glow(g, buf, t) {  g.detached("glow", () => {    for (const [wx, wy, ph] of WISPS) {      const p = (t + ph) % 1;      const x = wx + Math.sin(TAU * (p * 0.9 + ph)) * 1.6, z = FLOOR + 3 + p * 24;      const [sx, sy, dep] = project([x, wy, z]);      const px = Math.round(sx), py = Math.round(sy);      const put = (a, b, k) => { if (visible(buf, a, b, dep, 0.5)) g.pixel(a, b, k); };      if (p < 0.4) { put(px, py, "v2"); put(px + 1, py, "v2"); put(px, py + 1, "v1"); put(px + 1, py + 1, "v1"); put(px, py - 1, "v1"); }      else if (p < 0.72) { put(px, py, "v1"); put(px + 1, py - 1, "v2"); if (chk(px, py)) put(px - 1, py + 1, "v1"); }      else if (chk(px, Math.floor(t * 12))) put(px, py, "v1");    }  });  g.detached("motes", () => {    for (const [mx, my, ph] of MOTES) {      const p = (t + ph) % 1;      if (p > 0.85) continue;      const z = FLOOR + 1 + p * 22, x = mx + Math.round(Math.sin(TAU * (p + ph)) * 1.2);      const [sx, sy, dep] = project([x, my, z]);      const px = Math.round(sx), py = Math.round(sy);      if (!visible(buf, px, py, dep, 0.5)) continue;      g.pixel(px, py, p < 0.3 ? "n5" : p < 0.6 ? "v3" : "v2");    }  });}// the fallen lid's own dithered shadow on the floor to the rightfunction lidShadow(g) {  g.part("lidshadow", () => {    for (let y = GROUND - 12; y <= GROUND; y++) for (let x = Math.round(CX + 9); x <= Math.round(CX + 25) && x < 48; x++) {      const u = (x - (CX + 17)) / 8.5, v = (y - (GROUND - 6)) / 7;      if (u * u + v * v <= 1 && chk(x, y)) g.pixel(x, y, "ink");    }  });}// ---- statesconst CLOSED = { xc: RIM, phi: 0, yaw: 0, ty: 0, lift: 0 };const PHI_REST = Math.asin(HB / 17.6);const OPENED = { xc: -8.8, phi: PHI_REST, yaw: 0.08, ty: -1.2, lift: 0 };const OPEN_KEYS = [  // xc, phi, yaw, ty, lift  [RIM, 0, 0, 0, 0],  [RIM, 0, 0.03, 0, 1.6],          // a thump from inside: the lid jumps  [RIM - 2.2, 0, 0.05, -0.3, 0.3],   // and starts to slide  [RIM - 5.6, 0, 0.07, -0.6, 0],  [RIM - 9.5, 0.06, 0.08, -0.9, 0],  [-5.6, 0.2, 0.08, -1.1, 0],      // over the edge  [-8.0, 0.33, 0.08, -1.2, 0],  [-8.8, PHI_REST, 0.08, -1.2, 0],  // lands  [-8.8, PHI_REST - 0.07, 0.08, -1.2, 0], // bounces  [OPENED.xc, OPENED.phi, OPENED.yaw, OPENED.ty, OPENED.lift],   // == open-idle f0];// the knocks in the closed loop: [frame, lift, yaw, shiftX]const KNOCK = { 5: [1.4, 0, 0.5], 6: [0, 0, 0], 8: [2.2, 0.05, -0.6], 9: [0.4, 0.02, 0] };// violet light pooled on the cavity floor, breathing once per loop: 2 = core, 1 = dithered halo, 0 = nonefunction floorGlow(t) {  const k = 0.5 - 0.5 * Math.cos(TAU * t);  return (x, y, px, py) => {    const r = Math.hypot(x / 2.6, (y + 1) / 8);    if (r < 0.35 + 0.25 * k) return 2;    if (r < 0.7 + 0.3 * k && chk(px, py)) return 1;    return 0;  };}const Coffin = sprite(pal, (g, t, state) => {  contactShadow(g, 23, GROUND - 1, 12, 2);  if (state === "closed") {    const f = Math.round(t * N_IDLE) % N_IDLE;    const kk = KNOCK[f] || [0, 0, 0];    const ps = { xc: RIM - kk[2] * 0.6, phi: 0, yaw: kk[1], ty: 0, lift: kk[0] };    const shake = f === 8 ? 1 : 0;    drawCoffin(g, ps, { gem: f === 5 || f === 8 || f === 9 ? 1 : 0 }, shake);    g.detached("dust", () => {      // puffs squeezed out from under the lid at each knock, left and right seams and the foot corners      const seam = (y) => BASE - HB - K * y;      for (const [f0, amp] of [[5, 1], [8, 2]]) {        const p = (f - f0) / 4;        if (p < 0 || p >= 1) continue;        puff(g, Math.round(CX - 9), Math.round(seam(4)), p, -1);        puff(g, Math.round(CX + 8), Math.round(seam(-4)), p, 1);        if (amp > 1) { puff(g, Math.round(CX - 7), Math.round(seam(-14)), p, -1); puff(g, Math.round(CX + 6), Math.round(seam(12)), p, 1); }      }    });    return;  }  if (state === "open") {    const f = Math.min(N_OPEN - 1, Math.round(t * N_OPEN));    if (f >= 7) lidShadow(g);    const [xc, phi, yaw, ty, lift] = OPEN_KEYS[f];    const buf = drawCoffin(g, { xc, phi, yaw, ty, lift }, {      gem: f === 1 ? 1 : 0,      glow: f >= 3 ? floorGlow(f === N_OPEN - 1 ? 0 : 0.5) : null,    });    g.detached("dust", () => {      if (f >= 1 && f <= 3) { const p = (f - 1) / 3; puff(g, Math.round(CX - 9), Math.round(BASE - HB - K * 4), p, -1); }      if (f === 7 || f === 8) {                     // gone again on the last frame (== open-idle f0)        const p = (f - 7) / 2;        for (const yy of [-10, 2, 12]) puff(g, Math.round(CX + 20), Math.round(BASE - K * yy) - 1, p, 1);      }    });    if (f === N_OPEN - 1) glow(g, buf, 0);    return;  }  lidShadow(g);  const buf = drawCoffin(g, OPENED, { glow: floorGlow(t) });  glow(g, buf, t);});submit(Coffin, {  size: [48, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 23, y: GROUND },  variants: [    { name: "closed", args: ["closed"] },    { name: "open", args: ["open"], animate: { fps: 10, duration: N_OPEN / 10, loop: false } },    { name: "open-idle", args: ["open-idle"] },  ],});

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.