Tessarune
Arcane Curiosities Vol.1 · crystal_ball.js

Crystal ball

32×48 · 184 lines of JavaScript

A 32×48 crystal ball on a brass claw stand, in top-down 3/4 with the key light at the top-left. In the 16-frame loop, mist swirls inside the glass in two arms that make half a turn, an eye opens and fades back into the mist, and the specular glint twinkles once. The glow comes in blue, violet and green by swapping one ramp, with no new colours.

Crystal ball — Rendered output6×
Rendered by this program, variant “idle-violet” · 32×48 · 16 frames
Arcane Curiosities Vol.1Get 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.

crystal_ball.js · 1–4
// Crystal ball on a brass claw stand — 32x48, top-down 3/4, key light top-left.// Loop: 16 frames @ 10 fps. Mist swirls inside the glass (two arms, half a turn per loop), an eye opens and// fades back into the mist, the specular twinkles once.// Variants: glow colour blue / violet / green (ramp swap of the glow0..3 aliases — no new colours).

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

crystal_ball.js · 175–184
submit(CrystalBall, {  size: [32, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: 16, y: 45 },  variants: [    { name: "idle-blue" },    { name: "idle-violet", repalette: swap("glow", "v") },    { name: "idle-green", repalette: swap("glow", "e") },  ],});
Show the whole program (184 lines)
crystal_ball.js · 184 lines
// Crystal ball on a brass claw stand — 32x48, top-down 3/4, key light top-left.// Loop: 16 frames @ 10 fps. Mist swirls inside the glass (two arms, half a turn per loop), an eye opens and// fades back into the mist, the specular twinkles once.// Variants: glow colour blue / violet / green (ramp swap of the glow0..3 aliases — no new colours).const CX = 16, GROUND = 45;const ORB = { cx: 16, cy: 19, r: 11 };const BASE = { cy: 37, rx: 10, ry: 3, h: 4 };function orbAt(x, y) {  const nx = (x + 0.5 - ORB.cx) / ORB.r, ny = (y + 0.5 - ORB.cy) / ORB.r;  return { nx, ny, r: Math.sqrt(nx * nx + ny * ny) };}// ---- glass -----------------------------------------------------------------------------------------------function glass(g, t) {  const eyeOpen = 0.5 - 0.5 * Math.cos(TAU * t);  g.field({ x: [ORB.cx - ORB.r, ORB.cx + ORB.r], y: [ORB.cy - ORB.r, ORB.cy + ORB.r] }, (x, y) => {    const { nx, ny, r } = orbAt(x, y);    if (r > 1) return null;    const ux = nx / (r || 1), uy = ny / (r || 1);    // specular: a crisp window highlight on the upper-left shoulder    const sx = nx + 0.4, sy = ny + 0.42;    if (sx * sx * 26 + sy * sy * 44 < 1) return "n5";    // dark rim — the far (upper) rim is thicker: the eye looks through the full depth of glass there    if (r > 0.9) return "glow0";    if (r > 0.8 && uy < -0.35) return "glow0";    // reflection of the room: a thin bright arc just inside the upper-left rim, running on from the specular    if (r > 0.72 && r <= 0.8 && ux < -0.2 && uy < -0.35) return "glow3";    // light passing through: a caustic crescent on the lower inner wall, opposite the key light    const dir = ux * 0.42 + uy * 0.91;    if (r > 0.64 && dir > 0.9) return r > 0.7 && r < 0.86 && dir > 0.97 ? "glow3" : "glow2";    // swirling mist: two arms, 2-fold symmetric, half a turn per loop (closes the loop)    const inEye = Math.abs(nx) < 0.44 && Math.abs(ny) < 0.24 && eyeOpen > 0.3;    if (r < 0.72 && r > 0.14 && !inEye) {      const th = Math.atan2(ny, nx);      const m = Math.cos(2 * th + 5.4 * r - TAU * t);      if (m > 0.92 && r > 0.3 && r < 0.6) return "glow3";      if (m > 0.62) return "glow2";    }    // body of the glass: a shade darker on the upper-left (reflects the dark room), lighter where light exits    const d = 0.55 * nx + 0.83 * ny;    if (d > 0.4) return "glow2";    if (d < -0.62) return "glow0";    return "glow1";  });}function eye(g, t) {  // fades in with the mist parting, opens, and sinks back: periodic in t  const e = 0.5 - 0.5 * Math.cos(TAU * t);  if (e < 0.3) return;  const cx = ORB.cx, cy = ORB.cy;  g.part("eye", () => {    if (e < 0.55) {                                    // a slit of light      g.line([cx - 3, cy], [cx + 2, cy], "glow2");      return;    }    const open = e > 0.86;    const lid = open ? "glow3" : "glow2";    // almond lids    g.line([cx - 4, cy], [cx - 3, cy], lid);    g.line([cx + 2, cy], [cx + 3, cy], lid);    g.line([cx - 2, cy - 1 - (open ? 1 : 0)], [cx + 1, cy - 1 - (open ? 1 : 0)], lid);    g.line([cx - 2, cy + 1 + (open ? 1 : 0)], [cx + 1, cy + 1 + (open ? 1 : 0)], lid);    if (open) {      g.pixel(cx - 3, cy - 1, lid); g.pixel(cx + 2, cy - 1, lid);      g.pixel(cx - 3, cy + 1, lid); g.pixel(cx + 2, cy + 1, lid);      g.rect({ x: [cx - 2, cx + 1], y: [cy - 1, cy + 1] }, "glow2", { fill: "glow2" });      g.line([cx, cy - 1], [cx, cy + 1], "glow0");      // slit pupil      g.line([cx - 1, cy - 1], [cx - 1, cy + 1], "glow0");      g.pixel(cx - 2, cy - 1, "glow3");                  // catchlight    } else {      g.line([cx - 2, cy], [cx + 1, cy], "glow1");      g.pixel(cx - 1, cy, "glow0"); g.pixel(cx, cy, "glow0");    }  });}// ---- brass claw stand ------------------------------------------------------------------------------------const brassV = (u, v) => (v < -0.45 ? "g2" : v > 0.45 ? "g0" : "g1");const TALONS = {  L: [[13, 31], [9, 29], [6, 26], [5, 22], [7, 19]],  R: [[19, 31], [23, 29], [26, 26], [27, 22], [25, 19]],  F: [[16, 32], [16, 29], [15, 26]],};function talon(g, name, pts, w0, leftV) {  // a jointed claw: a knuckle crease part-way up, a polished nail at the tip.  // leftV = which sign of v (across the stroke) points screen-left, i.e. toward the key light.  g.part(name, () => g.sweep(pts, { w0, w1: 0.3, profile: 1.3 }, (u, v) => {    const s = v * leftV;    if (u > 0.42 && u < 0.5) return s > 0.3 ? "g1" : "g0";    if (u > 0.8) return s > -0.2 ? "g3" : "g2";    return s > 0.35 ? "g2" : s < -0.45 ? "g0" : "g1";  }));}function talons(g) {  talon(g, "talonL", TALONS.L, 1.5, 1);  talon(g, "talonR", TALONS.R, 1.5, -1);  talon(g, "talonF", TALONS.F, 1.6, -1);}function stem(g) {  g.part("stem", () => {    // domed foot on the wood, a slim neck, and the palm the talons grow from    g.ellipse({ x: [12, 20], y: [34, 38] }, "g0", { fill: "g0" });    g.ellipse({ x: [12, 20], y: [33, 37] }, "g1", { fill: "g1" });    g.line([13, 35], [14, 34], "g2"); g.line([18, 36], [19, 35], "g0");    g.rect({ x: [15, 17], y: [31, 34] }, "g1", { fill: "g1" });    g.line([15, 31], [15, 34], "g2"); g.line([17, 31], [17, 34], "g0");    g.ellipse({ x: [12, 20], y: [29, 32] }, "g1", { fill: "g1" });    g.line([13, 30], [15, 29], "g3"); g.line([17, 31], [19, 31], "g0");  });}function caustic(g) {  // light focused through the orb lands on the base, down-right of the stand (away from the key light)  g.part("caustic", () => {    g.line([21, 37], [23, 37], "glow2"); g.pixel(22, 37, "glow3"); g.pixel(22, 38, "glow1");  });}// ---- dark-wood base --------------------------------------------------------------------------------------function base(g) {  const { cy, rx, ry, h } = BASE;  g.part("base", () => {    // side band (front of the round plinth), then the lit top face    g.field({ x: [CX - rx, CX + rx], y: [cy, cy + h + ry] }, (x, y) => {      const nx = (x + 0.5 - CX) / rx;      if (Math.abs(nx) > 1) return null;      const yb = cy + h + ry * Math.sqrt(1 - nx * nx);   // bottom edge of the band      if (y + 0.5 > yb) return null;      const rel = y - (cy + ry * Math.sqrt(1 - nx * nx));      if (Math.abs(rel - 1.5) < 0.5) return nx < -0.3 ? "w1" : "w0";   // carved groove      if (nx < -0.55) return "w2";      if (nx < 0.35) return "w1";      return "w0";    });    g.field({ x: [CX - rx, CX + rx], y: [cy - ry, cy + ry] }, (x, y) => {      const nx = (x + 0.5 - CX) / rx, ny = (y + 0.5 - cy) / ry;      const d = nx * nx + ny * ny;      if (d > 1) return null;      const a = -0.75 * nx - 0.4 * ny;      if (d > 0.62) return a > 0.2 ? "w3" : a > -0.3 ? "w2" : "w1";   // bevelled rim      return a > 0.35 ? "w2" : "w1";    });  });}function twinkle(g, t) {  const p = (t + 0.3) % 1;  if (p > 0.18) return;  const x = ORB.cx - 5, y = ORB.cy - 6;  g.detached("twinkle", () => {    const k = p < 0.06 || p > 0.12 ? 1 : 2;    for (let i = 1; i <= k; i++) {      g.pixel(x - i - 1, y, "n5"); g.pixel(x + i + 1, y, "n5");      g.pixel(x, y - i - 1, "n5"); g.pixel(x, y + i + 1, "n5");    }  });}const CrystalBall = sprite(pal, (g, t) => {  contactShadow(g, CX, GROUND, 12, 2);  g.outlined("ink", () => {    base(g);    caustic(g);    stem(g);    g.part("orb", () => glass(g, t));    talons(g);  });  eye(g, t);  twinkle(g, t);});submit(CrystalBall, {  size: [32, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 1.6, loop: true },  anchor: { x: 16, y: 45 },  variants: [    { name: "idle-blue" },    { name: "idle-violet", repalette: swap("glow", "v") },    { name: "idle-green", repalette: swap("glow", "e") },  ],});

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.