Mana crystal
A 48×48 mana crystal cluster: five faceted spires fanning out of a cleft in a rough stone, each a prism with a lit, a mid and a shadow face, brighter facets at the tip and a bright ridge line. In the 12-frame loop one pulse of inner light climbs every spire at the same speed from the rock, so the short spires flare first and the tall one last, and a sparkle twinkles off three tips as it arrives. “idle-blue”, “idle-violet”, “idle-green” and “idle-red” swap the whole glow ramp.
6×
Get the animated sprites: Arcane Curiosities Vol.1 on itch.io12 animated props · 52 animations
The header. The comment at the top of the file is the brief: size, view, light direction and every variant.
// Mana crystal cluster — 48x48, top-down 3/4, key light top-left.// Five faceted spires fan out of a cleft in a rough low-poly stone. Each spire is a prism (lit / mid / shadow face,// hard planes) with brighter termination facets at the tip and a bright ridge line where the lit face meets the mid.// Loop: 12 frames @ 10 fps. One pulse of inner light climbs every spire at the same speed from the rock, so the// short spires flare first and the tall one last; a sparkle twinkles off three tips as the pulse arrives.// Variants: idle-blue / idle-violet / idle-green / idle-red (whole glow ramp swapped — no new colours).The last lines. submit declares the canvas and lists the variants the renderer exports.
submit(ManaCrystal, { size: [48, 48], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: 1.2, loop: true }, anchor: { x: 24, y: 45 }, variants: [ { name: "idle-blue" }, { name: "idle-violet", repalette: swap("glow", "v") }, { name: "idle-green", repalette: swap("glow", "e") }, { name: "idle-red", repalette: swap("glow", "r") }, ],});Show the whole program (195 lines)
// Mana crystal cluster — 48x48, top-down 3/4, key light top-left.// Five faceted spires fan out of a cleft in a rough low-poly stone. Each spire is a prism (lit / mid / shadow face,// hard planes) with brighter termination facets at the tip and a bright ridge line where the lit face meets the mid.// Loop: 12 frames @ 10 fps. One pulse of inner light climbs every spire at the same speed from the rock, so the// short spires flare first and the tall one last; a sparkle twinkles off three tips as the pulse arrives.// Variants: idle-blue / idle-violet / idle-green / idle-red (whole glow ramp swapped — no new colours).const W = 48, H = 48, CX = 24, GROUND = 45;const SPEED = 50; // px per loop the pulse climbsconst GLOW = ["glow0", "glow1", "glow2", "glow3"];function pip(px, py, poly) { let c = false; for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) { const [xi, yi] = poly[i], [xj, yj] = poly[j]; if ((yi > py) !== (yj > py) && px < ((xj - xi) * (py - yi)) / (yj - yi) + xi) c = !c; } return c;}// ---- crystal spires ---------------------------------------------------------------------------------------function spire(name, b, a, hw, tip, spark) { const dx = a[0] - b[0], dy = a[1] - b[1], L = Math.hypot(dx, dy); const d = [dx / L, dy / L], p = [-d[1], d[0]]; // d: up the axis, p: to the right of it const local = (x, y) => { const rx = x + 0.5 - b[0], ry = y + 0.5 - b[1]; return [rx * d[0] + ry * d[1], rx * p[0] + ry * p[1]]; }; const halfW = (u) => (u <= L - tip ? hw : hw * Math.max(0, (L - u) / tip)); const inside = (x, y) => { const [u, v] = local(x, y); return u <= L && u >= -1 && Math.abs(v) <= halfW(u) + 0.3; }; // static facet data per pixel; only the pulse and the flare depend on t const facet = (x, y) => { const [u, v] = local(x, y); const hwu = Math.max(0.7, halfW(u)), s = v / hwu; const tipFacet = u > L - tip * (1 - 0.3 * s); // termination facets; the ridge dips toward the lit side const face = s < -0.3 ? 0 : s < 0.38 ? 1 : 2; // lit / mid / shadow plane let k = (tipFacet ? [3, 2, 1] : [2, 1, 0])[face]; if (hw >= 4 && !tipFacet && face === 2 && Math.abs(s - 0.78) * hwu < 0.5) k = 1; // inner reflection (wide spire) const edge = Math.abs(v + 0.3 * hwu) < 0.5 && u < L - 1; // bright facet edge (lit | mid) return { u, k, tipFacet, edge }; }; const sp = { name, kind: "crystal", group: name, b, a, hw, tip, spark, L, d, inside, facet }; sp.tone = (x, y, t) => { const f = sp.data[y * W + x]; if (f.edge) return "glow3"; let k = f.k; const du = f.u - pulseCentre(t); if (du > -3 && du < 2) k = Math.min(3, k + 1); // the rising light band if (f.tipFacet && (t - arrival(sp) + 1) % 1 < 0.12) k = Math.min(3, k + 1); // tip flares as it arrives return GLOW[k]; }; return sp;}const pulseCentre = (t) => t * SPEED - 4; // u (px up the axis) where the pulse is nowconst arrival = (sp) => (sp.L + 4) / SPEED; // t at which the pulse reaches the apexconst SPIRES = { // a fan from one cleft: feet close together, tips spread bl: spire("spireBackL", [20, 39], [13, 13], 3.1, 6, [-3, -1]), br: spire("spireBackR", [28, 39], [35, 17], 3.2, 6, null), c: spire("spireTall", [24, 41], [24, 5], 4.5, 8, [-4, 0]), fl: spire("spireFrontL", [17, 42], [9, 28], 2.7, 4.5, [-3, 0]), fr: spire("spireFrontR", [32, 43], [40, 29], 2.8, 4.5, null),};// ---- stone: a crown behind the cleft, a front face below it, two raised shoulders in front -----------------// Each rock is a list of hand-cut planes (first hit wins); rim:true lets the plane's top-left edge catch the light.function rock(name, group, planes, glowRim) { const at = (x, y) => planes.find((f) => pip(x + 0.5, y + 0.5, f.poly)); return { name, kind: "rock", group, planes, at, inside: (x, y) => !!at(x, y), glowRim };}const CROWN = rock("crown", "mound", [ { tone: "n3", rim: true, poly: [[13, 41], [15, 36], [19, 33], [29, 33], [33, 36], [35, 41]] },], true);const FACE = rock("frontFace", "mound", [ { tone: "n2", poly: [[14, 40.5], [21, 41], [27, 41], [34, 40.5], [35, 46], [13, 46]] },]);const SHOULDER_L = rock("rockLeft", "left", [ { tone: "n4", poly: [[4, 41], [5, 37.5], [8, 35], [9.5, 37], [8, 40.5]] }, // lit bevel { tone: "n3", rim: true, poly: [[5, 37.5], [8, 35], [12, 33.5], [15.5, 35], [17.5, 38.5], [13, 40], [8, 40.5]] }, // crown { tone: "n3", poly: [[4, 41], [8, 40.5], [11, 40.2], [10, 46], [6, 45]] }, // front, lit { tone: "n2", poly: [[11, 40.2], [13, 40], [17.5, 38.5], [18, 42], [16.5, 45], [13, 46.2], [10, 46]] }, // front, turning]);const SHOULDER_R = rock("rockRight", "right", [ { tone: "n3", rim: true, poly: [[30.5, 40.5], [32, 37.5], [36, 35.5], [40.5, 36], [43.5, 39], [38, 40.5], [34, 41]] }, // crown { tone: "n1", poly: [[30.5, 40.5], [34, 41], [38, 40.5], [37, 46.2], [32, 46], [30, 43.5]] }, // front { tone: "n0", poly: [[38, 40.5], [43.5, 39], [44.5, 42], [42, 45.2], [37, 46.2]] }, // side, in shadow]);// painter's order, back to frontconst SHAPES = [CROWN, SPIRES.bl, SPIRES.br, SPIRES.c, SPIRES.fl, SPIRES.fr, FACE, SHOULDER_R, SHOULDER_L];// ---- precompute everything static once: masks, seams, facet data --------------------------------------------const INK = 1, CREVICE = 2, BODY = 3;for (const sh of SHAPES) { sh.mask = new Uint8Array(W * H); for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) if (sh.inside(x, y)) sh.mask[y * W + x] = 1;}const inS = (sh, x, y) => x >= 0 && y >= 0 && x < W && y < H && sh.mask[y * W + x] === 1;SHAPES.forEach((sh, k) => { sh.code = new Uint8Array(W * H); if (sh.kind === "crystal") sh.data = []; else sh.plane = []; let x0 = W, x1 = 0, y0 = H, y1 = 0; for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) { if (!inS(sh, x, y)) continue; x0 = Math.min(x0, x); x1 = Math.max(x1, x); y0 = Math.min(y0, y); y1 = Math.max(y1, y); let code = BODY; // seam where this shape's edge lies over what is already visible there (the nearer shape owns the line) for (const [ax, ay] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) { if (inS(sh, ax, ay)) continue; let j = k - 1; while (j >= 0 && !inS(SHAPES[j], ax, ay)) j--; if (j < 0 || SHAPES[j].group === sh.group) continue; code = SHAPES[j].kind === "rock" && sh.kind === "rock" ? CREVICE : INK; break; } sh.code[y * W + x] = code; if (sh.kind === "crystal") sh.data[y * W + x] = sh.facet(x, y); else { const f = sh.at(x, y); const rimLit = f.rim && (!inS(sh, x - 1, y) || !inS(sh, x, y - 1)) && x < 30; const halo = sh.glowRim && !Object.values(SPIRES).some((sp) => inS(sp, x, y)) && [[1, 0], [-1, 0], [0, 1]].some(([ox, oy]) => Object.values(SPIRES).some((sp) => inS(sp, x + ox, y + oy))); sh.plane[y * W + x] = halo ? "halo" : rimLit ? "n4" : f.tone; } } sh.box = { x: [x0, x1], y: [y0, y1] }; if (sh.kind === "rock") sh.tone = (x, y, t) => { const c = sh.plane[y * W + x]; return c === "halo" ? ((t + 0.98) % 1 < 0.2 ? "glow2" : "glow1") : c; // crystal light spilled on the stone };});function drawShape(g, sh, t) { g.part(sh.name, () => g.field(sh.box, (x, y) => { const c = sh.code[y * W + x]; return c === 0 ? null : c === INK ? "ink" : c === CREVICE ? "n0" : sh.tone(x, y, t); }));}function rockMarks(g, t) { g.part("rockMarks", () => { g.line([22, 43], [23, 44], "n1"); g.pixel(23, 45, "n1"); // crack down the front face g.pixel(28, 42, "n3"); g.pixel(19, 44, "n1"); g.pixel(11, 36, "n2"); g.pixel(12, 37, "n4"); g.pixel(38, 38, "n2"); // pits on the shoulders g.pixel(7, 43, "n2"); g.pixel(34, 44, "n0"); }); g.part("shard", () => { // a small shard in the left shoulder const hot = t % 1 < 0.2; g.fillPoly([[9, 41], [11, 43], [9, 45], [7, 43]], "ink"); g.pixel(9, 42, hot ? "glow3" : "glow2"); g.pixel(8, 43, "glow3"); g.pixel(9, 43, hot ? "glow2" : "glow1"); g.pixel(10, 43, "glow0"); g.pixel(9, 44, "glow1"); });}function sparkles(g, t) { g.detached("sparkles", () => { for (const sp of Object.values(SPIRES)) { if (!sp.spark) continue; const p = (t - arrival(sp) + 1) % 1; const x = Math.round(sp.a[0]) + sp.spark[0], y = Math.round(sp.a[1]) + sp.spark[1]; const star = (n, c) => { for (let i = 1; i <= n; i++) for (const [ox, oy] of [[i, 0], [-i, 0], [0, i], [0, -i]]) g.pixel(x + ox, y + oy, i === n && n > 1 ? "glow2" : "glow3"); g.pixel(x, y, c); }; if (p < 0.08) g.pixel(x, y, "glow3"); // dot -> cross -> big star (held) -> cross -> dot else if (p < 0.16) star(1, "n5"); else if (p < 0.33) star(2, "n5"); else if (p < 0.42) star(1, "glow3"); else if (p < 0.5) g.pixel(x, y, "glow2"); } });}const ManaCrystal = sprite(pal, (g, t) => { contactShadow(g, CX, GROUND, 17, 2); g.outlined("ink", () => { for (const sh of SHAPES) drawShape(g, sh, t); rockMarks(g, t); }); sparkles(g, t);});submit(ManaCrystal, { size: [48, 48], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: 1.2, loop: true }, anchor: { x: 24, y: 45 }, variants: [ { name: "idle-blue" }, { name: "idle-violet", repalette: swap("glow", "v") }, { name: "idle-green", repalette: swap("glow", "e") }, { name: "idle-red", repalette: swap("glow", "r") }, ],});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.