Stained-glass window
A 32×64 wall prop with no contact shadow: a pointed stone arch over quoined jambs and a projecting sill, glazed with many small leaded panes in an abstract pattern (a coloured border, a rose with a four-point star, two diamond medallions). Every pane is bevelled, its top-left pixel one step lighter and its bottom-right one step darker. All variants are 16-frame loops: “day” sweeps a shimmer band across the glass while a dithered light shaft with dust motes spills below the sill; “night” leaves the glass dark while a few moonlit panes breathe a faint blue glow; “broken” opens shattered panes onto darkness, with a glinting shard.
6×
Get 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.
// Stained-glass lancet window — 32x64 WALL prop (no contact shadow), 3/4 view, key light top-left.// A pointed (two-centre) arch of dressed stone voussoirs over quoined jambs and a projecting sill. The glass is// leaded into many small panes: a coloured border band, a rose with a four-point star in the arch head, and two// diamond medallions over quarries below an iron saddle bar. Pattern is abstract (no figures).// Every pane is bevelled: its top-left pixel one step lighter, its bottom-right one step darker.// Variants: day (16f loop: a shimmer band sweeps the glass; a dithered light shaft with drifting dust motes spills// below the sill), night (16f loop: dark muted glass, a few moonlit panes breathe a faint blue glow),// broken (16f loop: shattered panes open onto darkness, cracks, a shard glints on the glass and on the sill).The last lines. submit declares the canvas and lists the variants the renderer exports.
submit(StainedWindow, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: 1.6, loop: true }, anchor: { x: MX, y: BOT + 4 }, args: ["day"], variants: [ { name: "day" }, { name: "night", args: ["night"] }, { name: "broken", args: ["broken"] }, ],});Show the whole program (271 lines)
// Stained-glass lancet window — 32x64 WALL prop (no contact shadow), 3/4 view, key light top-left.// A pointed (two-centre) arch of dressed stone voussoirs over quoined jambs and a projecting sill. The glass is// leaded into many small panes: a coloured border band, a rose with a four-point star in the arch head, and two// diamond medallions over quarries below an iron saddle bar. Pattern is abstract (no figures).// Every pane is bevelled: its top-left pixel one step lighter, its bottom-right one step darker.// Variants: day (16f loop: a shimmer band sweeps the glass; a dithered light shaft with drifting dust motes spills// below the sill), night (16f loop: dark muted glass, a few moonlit panes breathe a faint blue glow),// broken (16f loop: shattered panes open onto darkness, cracks, a shard glints on the glass and on the sill).const W = 32, H = 64, MX = 16; // MX: the window's vertical axis (between x 15 and 16)const S = 21; // springing line of the archconst GL = 7, GR = 25, R = 17; // glass spans [GL, GR); arch radius (lancet: R well above half-width)const FR = 4; // stone frame thicknessconst BOT = 40; // glass/frame bottom (first sill row)const C_L = GL + R, C_R = GR - R; // arc centres (the left arc is centred on the right, and vice versa)function inArch(x, y, grow) { const px = x + 0.5, py = y + 0.5, r = R + grow; if (px < GL - grow || px > GR + grow || y >= BOT) return false; if (py >= S) return true; return (px - C_L) ** 2 + (py - S) ** 2 <= r * r && (px - C_R) ** 2 + (py - S) ** 2 <= r * r;}const inGlass = (x, y) => inArch(x, y, 0);function ringOf(x, y) { // 0 = touches the stone, 1, 2 ... = distance inward (4-neighbour, in whole px) for (let d = 1; d <= 3; d++) { if (!inGlass(x - d, y) || !inGlass(x + d, y) || !inGlass(x, y - d)) return d - 1; } return 3;}// ---- glass pattern -----------------------------------------------------------------------------------// returns "lead", "bar" or { ramp, pane } where pane is a stable id (for per-pane tweaks)const ROSE = { cx: 16, cy: 16.4, r: 5.2 };const HEAD_Y = 22, BAR_Y = 31;function paneAt(x, y) { if (!inGlass(x, y)) return null; const ring = ringOf(x, y); if (ring === 0) return "lead"; if (ring === 1) { // border band: short alternating red / blue pieces with lead joints if (y >= S) { if ((y - S) % 4 === 3) return "lead"; const s = Math.floor((y - S) / 4) + (x < MX ? 0 : 1); return { ramp: s % 2 ? "r" : "b", pane: 100 + s * 2 + (x < MX ? 0 : 1) }; } const s = Math.floor((y + 40) / 3) + (x < MX ? 0 : 1); if ((y + 40) % 3 === 2) return "lead"; return { ramp: s % 2 ? "r" : "b", pane: 200 + s * 2 + (x < MX ? 0 : 1) }; } if (ring === 2) return "lead"; if (y <= HEAD_Y) { if (y === HEAD_Y) return "lead"; const dx = x + 0.5 - ROSE.cx, dy = y + 0.5 - ROSE.cy, r = Math.hypot(dx, dy); if (r > ROSE.r) return { ramp: "v", pane: dx < 0 ? 1 : 2 }; // spandrels if (r > ROSE.r - 1) return "lead"; if (r < 1.3) return { ramp: "g", pane: 3 }; // boss const star = Math.abs(dx) + Math.abs(dy); if (Math.abs(star - 3.1) < 0.5) return "lead"; if (star < 3.1) return { ramp: "r", pane: 4 }; // four-point star if (Math.abs(Math.abs(dx) - Math.abs(dy)) < 0.6) return "lead"; // petal joints on the diagonals return { ramp: "b", pane: dx < 0 ? (dy < 0 ? 5 : 6) : (dy < 0 ? 7 : 8) }; } if (y === BAR_Y) return "bar"; // iron saddle bar // two diamond medallions, the corners filled with quarries const top = y < BAR_Y, cy = top ? 27 : 36, dx = x + 0.5 - MX, dy = y + 0.5 - cy; const dia = Math.abs(dx) / 1.15 + Math.abs(dy); if (Math.abs(dia - 4.1) < 0.5) return "lead"; if (dia < 4.1) { if (dia < 1.5) return { ramp: "g", pane: top ? 10 : 11 }; // jewel return { ramp: top ? "e" : "r", pane: top ? 12 : 13 }; } if (Math.abs(dy) < 0.6) return "lead"; const q = (dx < 0 ? 0 : 1) + (dy < 0 ? 0 : 2); return { ramp: (q === 0 || q === 3) === top ? "b" : "v", pane: 30 + (top ? 0 : 4) + q };}// precomputed per pixel (the pattern never changes): pane lookup + bevel roleconst GLASS = [];for (let y = 0; y < BOT; y++) { GLASS.push([]); for (let x = 0; x < W; x++) GLASS[y].push(paneAt(x, y));}const cell = (x, y) => (y >= 0 && y < BOT && x >= 0 && x < W ? GLASS[y][x] : null);const isLead = (c) => c === null || typeof c === "string";function bevel(x, y) { // +1 top-left edge of a pane (the lead above or left of it), -1 bottom-right edge const up = isLead(cell(x, y - 1)), lf = isLead(cell(x - 1, y)); const dn = isLead(cell(x, y + 1)), rt = isLead(cell(x + 1, y)); if (up || lf) return (dn || rt) && !(up && lf) ? 0 : 1; if (dn || rt) return -1; return 0;}// ---- broken variant geometry -------------------------------------------------------------------------const HOLES = [ [[9, 33], [12, 32], [13, 34], [16, 33], [15, 36], [18, 38], [14, 39], [12, 37], [9, 39]], [[18, 24], [21, 23], [22, 26], [21, 29], [20, 27], [17, 28]], [[17, 13], [20, 13], [20, 17], [18, 15]],];function inPoly(px, py, P) { let inside = false; for (let i = 0, j = P.length - 1; i < P.length; j = i++) { const [xi, yi] = P[i], [xj, yj] = P[j]; if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) inside = !inside; } return inside;}const inHole = (x, y) => HOLES.some((P) => inPoly(x + 0.5, y + 0.5, P));const CRACKS = [[[14, 32], [19, 29]], [[16, 36], [21, 39]], [[18, 24], [14, 23]], [[10, 33], [10, 28]], [[17, 13], [14, 11]]];function onCrack(x, y) { for (const [[x0, y0], [x1, y1]] of CRACKS) { const n = Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0)); for (let i = 0; i <= n; i++) if (Math.round(x0 + ((x1 - x0) * i) / n) === x && Math.round(y0 + ((y1 - y0) * i) / n) === y) return true; } return false;}// ---- glass colour ------------------------------------------------------------------------------------const clampI = (i) => Math.max(0, Math.min(3, i));function moonlit(x, y) { // a few panes at the upper right catch the moon return x - MX - (y - 14) * 0.5 > 1.5 && y < 31;}function glassKey(x, y, mode, t) { const c = cell(x, y); if (c === null) return null; if (mode === "broken" && inHole(x, y)) { // darkness through the hole, with a faint grey interior edge top-left (the reveal behind) return !inHole(x - 1, y) || !inHole(x, y - 1) ? "n0" : "ink"; } if (c === "lead") return "ink"; if (c === "bar") return mode === "night" ? "n1" : x < MX ? "n3" : "n2"; const bv = bevel(x, y); if (mode === "day") { // shimmer band sweeping diagonally (top-left -> bottom-right), off-glass at t=0 and t=1 const p = -16 + t * 70, d = x + y * 0.5 - p; const band = Math.abs(d) < 4 ? (Math.abs(d) < 1.5 ? 2 : 1) : 0; if (band === 2 && bv >= 0) return c.ramp === "g" ? "g3" : "n5"; return `${c.ramp}${clampI(2 + bv + band)}`; } if (mode === "night") { if (moonlit(x, y)) { const pulse = 0.5 + 0.5 * Math.cos(TAU * t + (x + y) * 0.35); return `b${clampI((pulse > 0.55 ? 2 : 1) + bv)}`; } return `${c.ramp}${bv > 0 ? 1 : 0}`; } // broken: overcast daylight; fresh edges and cracks catch the light if (HOLES.length && (inHole(x - 1, y) || inHole(x + 1, y) || inHole(x, y - 1) || inHole(x, y + 1))) return `${c.ramp}3`; if (onCrack(x, y)) return "n4"; return `${c.ramp}${clampI(1 + bv)}`;}// ---- stone frame -------------------------------------------------------------------------------------function stoneKey(x, y) { if (!inArch(x, y, FR) || inGlass(x, y)) return null; const px = x + 0.5, py = y + 0.5, left = px < MX; // reveal ring next to the glass: the left/top reveal is in shade, the right reveal catches the light const nearGlass = inGlass(x + 1, y) || inGlass(x - 1, y) || inGlass(x, y + 1); if (nearGlass) return left || py < S - 6 ? (left ? "n0" : "n1") : "n4"; // outer arris: lit on the left/top, shaded on the right const outer = !inArch(x - 1, y, FR) || !inArch(x + 1, y, FR) || !inArch(x, y - 1, FR); if (outer) return left || py < 5 ? "n4" : "n1"; let id, joint, topEdge; if (py < S) { // voussoirs: radial joints about the arch axis const a = Math.atan2(py - S, px - MX); // -PI .. 0 const u = (a + Math.PI) / (Math.PI / 7); id = Math.floor(u); const f = u - id; joint = f < 0.09 || f > 0.97; topEdge = f < 0.2 ? !left : f > 0.85 ? left : false; if (id === 3 && py < 4) return "n4"; // keystone crown } else { // jamb courses with alternating long/short blocks const row = y - S, course = Math.floor(row / 5); joint = row % 5 === 4 || (course % 2 === 1 && (x === 5 || x === 26)); topEdge = row % 5 === 0; id = 10 + course * 2 + (left ? 0 : 1); } if (joint) return "n1"; if (topEdge) return left ? "n4" : "n3"; const dark = id % 3 === 2; // one stone in three is a shade darker return left ? (dark ? "n2" : "n3") : (dark ? "n1" : "n2");}function sill(g) { g.part("sill", () => { g.rect({ x: [2, 29], y: [BOT, BOT + 4] }, "n2", { fill: "n2" }); g.line([2, BOT], [29, BOT], "n4"); // top face, lit g.line([3, BOT + 1], [29, BOT + 1], "n3"); g.line([2, BOT + 1], [2, BOT + 4], "n3"); // lit left end g.line([29, BOT + 1], [29, BOT + 4], "n1"); g.line([3, BOT + 4], [29, BOT + 4], "n1"); // drip edge g.line([GL, BOT + 1], [GR - 1, BOT + 1], "n2"); // the glass's shadow on the sill g.pixel(7, BOT + 2, "n1"); g.pixel(20, BOT + 3, "n3"); g.pixel(24, BOT + 2, "n1"); // chips });}// ---- effects -----------------------------------------------------------------------------------------const BAYER = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];const SHAFT_Y0 = BOT + 5, SHAFT_Y1 = H - 1;const shaftL = (y) => GL + (y - SHAFT_Y0) * 0.3, shaftR = (y) => GR + (y - SHAFT_Y0) * 0.35;function lightShaft(g, t) { const swell = 0.5 + 0.5 * Math.cos(TAU * t); g.detached("lightShaft", () => { for (let y = SHAFT_Y0; y <= SHAFT_Y1; y++) { const k = (y - SHAFT_Y0) / (SHAFT_Y1 - SHAFT_Y0); const xl = shaftL(y), xr = shaftR(y); for (let x = Math.ceil(xl); x <= Math.floor(xr); x++) { // translucent checker at the window, fading (ordered dither) toward the floor const edge = Math.min(x - xl, xr - x); if (!chk(x, y)) continue; const fade = BAYER[(y >> 1) & 3][(x >> 1) & 3] / 16; if (edge < 1.2 && (x & 2)) continue; if (fade > 1.15 - k * 1.1 + 0.08 * swell) continue; let key = k < 0.35 ? "g3" : "n5"; if (k > 0.6) key = ["b3", "v3", "g3", "r3"][Math.min(3, Math.floor(((x - xl) / (xr - xl)) * 4))]; g.pixel(x, y, key); } } }); g.detached("dust", () => { for (let i = 0; i < 6; i++) { const p = (t + i / 6) % 1; const y = Math.round(SHAFT_Y0 + 1 + p * 16); const x = Math.round((shaftL(y) + shaftR(y)) / 2 + [-5, 2, -2, 5, 0, -4][i] + Math.sin(TAU * (t + i * 0.37)) * 1.5); if (p > 0.85 && !chk(x, i)) continue; g.pixel(x, y, "g3"); g.pixel(x + 1, y, "n5"); } });}function glint(g, x, y, f) { // 4-frame star: dot, cross, long cross, dot if (f < 0 || f > 3) return; g.pixel(x, y, "n5"); if (f === 1 || f === 2) for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) g.pixel(x + dx, y + dy, f === 2 ? "g3" : "n5"); if (f === 2) for (const [dx, dy] of [[2, 0], [-2, 0], [0, 2], [0, -2]]) g.pixel(x + dx, y + dy, "g3");}function brokenFx(g, t) { const f = Math.round(t * 16) % 16; g.part("shardOnSill", () => { g.pixel(21, BOT, "b3"); g.pixel(22, BOT, "b2"); g.pixel(22, BOT + 1, "b1"); g.pixel(23, BOT + 1, "b2"); g.pixel(10, BOT + 1, "r2"); g.pixel(11, BOT + 1, "r1"); }); g.detached("glint", () => { glint(g, 13, 34, f - 2); // on the big hole's jagged edge glint(g, 22, BOT, f - 10); // on the fallen shard });}// ---- sprite ------------------------------------------------------------------------------------------const StainedWindow = sprite(pal, (g, t, mode) => { g.outlined("ink", () => { g.part("frame", () => g.field({ x: [0, W - 1], y: [0, BOT - 1] }, (x, y) => stoneKey(x, y))); g.part("glass", () => g.field({ x: [GL, GR - 1], y: [0, BOT - 1] }, (x, y) => glassKey(x, y, mode, t))); sill(g); }); if (mode === "day") lightShaft(g, t); if (mode === "broken") brokenFx(g, t);});submit(StainedWindow, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: 1.6, loop: true }, anchor: { x: MX, y: BOT + 4 }, args: ["day"], variants: [ { name: "day" }, { name: "night", args: ["night"] }, { name: "broken", args: ["broken"] }, ],});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.