Tessarune
Arcane Curiosities Vol.1 · orrery.js

Orrery

48×64 · 230 lines of JavaScript

A 48×64 floor prop: an orrery with a glowing sun on a brass column over a turned dark-wood pedestal. Three planets (iron, a ringed violet one and a blue one) ride tilted brass rings at 3, 2 and 1 turns per loop, each held by a brass arm from its own collar on the column and lit by the sun they circle. The near halves of the rings, arms and planets pass in front of the sun and column, the far halves behind. The loop is 24 frames; “idle” has a fire sun and “idle-soul” swaps it to soulfire.

Orrery — Rendered output6×
Rendered by this program, variant “idle” · 48×64 · 24 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.

orrery.js · 1–5
// Orrery — 48x64 floor prop, top-down 3/4, key light top-left (the planets are lit by the sun they circle).// A glowing sun on a brass column over a turned dark-wood pedestal; three planets ride tilted brass rings at// 3, 2 and 1 turns per loop (iron, ringed violet, blue), each held by a brass arm from its own hub collar on the// column. Near halves of rings/arms/planets pass in front of the sun and column, far halves behind (g.depth).// Loop: 24 frames @ 10 fps. Variants: idle (fire sun), idle-soul (fl ramp swapped to soulfire).

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

orrery.js · 222–230
submit(Orrery, {  size: [48, 64], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 2.4, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    { name: "idle" },    { name: "idle-soul", repalette: swap("fl", "soul") },  ],});
Show the whole program (230 lines)
orrery.js · 230 lines
// Orrery — 48x64 floor prop, top-down 3/4, key light top-left (the planets are lit by the sun they circle).// A glowing sun on a brass column over a turned dark-wood pedestal; three planets ride tilted brass rings at// 3, 2 and 1 turns per loop (iron, ringed violet, blue), each held by a brass arm from its own hub collar on the// column. Near halves of rings/arms/planets pass in front of the sun and column, far halves behind (g.depth).// Loop: 24 frames @ 10 fps. Variants: idle (fire sun), idle-soul (fl ramp swapped to soulfire).const SX = 24, SY = 20, GROUND = 61;        // sun centre (continuous coords), ground rowconst SUN_R = 7;const HUB = { top: 32, bot: 36 };           // brass hub drum on the column; the arms leave from itconst RINGS = [  { name: "iron", r: 10.5, k: 0.46, roll: 13, speed: 3, ph: 0.15, hub: 34, rad: 2.5 },  { name: "violet", r: 14.5, k: 0.46, roll: -8, speed: 2, ph: 0.62, hub: 34, rad: 2.5 },  { name: "blue", r: 18.5, k: 0.46, roll: 4, speed: 1, ph: 0.9, hub: 34, rad: 3.5 },];// a point on a ring (angle a); front = sin(a): +1 nearest the camera (bottom of the ellipse on screen)function orbitPt(R, a) {  const c = Math.cos(a), s = Math.sin(a), q = (R.roll * Math.PI) / 180;  const ex = R.r * c, ey = R.r * R.k * s;  return { x: SX + ex * Math.cos(q) - ey * Math.sin(q), y: SY + ex * Math.sin(q) + ey * Math.cos(q), front: s };}// ring as a clean 1px pixel loop (sampled densely, deduplicated, L-corners removed), each pixel tagged near/farfunction ringPixels(R) {  const pts = [];  for (let i = 0; i < 480; i++) {    const a = (i / 480) * TAU, p = orbitPt(R, a);    const x = Math.floor(p.x), y = Math.floor(p.y), last = pts[pts.length - 1];    if (!last || last.x !== x || last.y !== y) pts.push({ x, y, front: p.front, u: Math.cos(a) });  }  while (pts.length > 1 && pts[0].x === pts[pts.length - 1].x && pts[0].y === pts[pts.length - 1].y) pts.pop();  const out = [];  for (let i = 0; i < pts.length; i++) {    const p = out.length ? out[out.length - 1] : pts[pts.length - 1], c = pts[i], q = pts[(i + 1) % pts.length];    const corner = (p.x === c.x || p.y === c.y) && (q.x === c.x || q.y === c.y) && p.x !== q.x && p.y !== q.y;    if (!corner) out.push(c);  }  return out;}const RING_PX = RINGS.map(ringPixels);// ---- pedestal ------------------------------------------------------------------------------------------// dark wood: the body lives in w0..w2, w3 only on the lit edgeconst stemR = (y) => (y <= 44 ? 4 : y <= 47 ? 2.5 : y <= 51 ? 4.5 - Math.abs(y - 49.5) * 0.6 : 3);function pedestal(g) {  g.part("stem", () => {    g.field({ x: [SX - 6, SX + 5], y: [42, 53] }, (x, y) => {      const R = stemR(y), u = (x + 0.5 - SX) / R;      if (Math.abs(u) > 1) return null;      if (y === 42) return u < -0.2 ? "g3" : u < 0.5 ? "g2" : "g1";   // brass ring capping the stem      if (y === 43) return u < -0.2 ? "g1" : "g0";      return u < -0.65 ? "w3" : u < -0.1 ? "w2" : u < 0.55 ? "w1" : "w0";    });    g.line([SX - 3, 45], [SX + 2, 45], "w0");                       // shadow under the cap's lip  });  // base: a short cylinder, top ellipse centred at yt, seen from above  const yt = 55, rx = 11.5, ry = 3.2, h = 3;  g.part("base", () => {    g.field({ x: [SX - 13, SX + 12], y: [51, GROUND] }, (x, y) => {      const u = (x + 0.5 - SX) / rx;      if (Math.abs(u) > 1) return null;      const e = ry * Math.sqrt(1 - u * u), yc = y + 0.5;      if (yc >= yt - e && yc <= yt + e) {                            // top face: lit toward the top-left        const v = (yc - yt) / ry;        const a = -0.6 * u - 0.5 * v;        if (yc < yt - e + 1 && u < 0.2) return "w3";                   // lit far rim        return a > 0.3 ? "w2" : a > -0.35 ? "w1" : "w0";      }      if (yc > yt && yc <= yt + h + e) {                             // side        if (yc > yt + h + e - 1) return "w0";        if (yc <= yt + e + 1) return u < -0.3 ? "g2" : u < 0.4 ? "g1" : "g0";   // brass trim band        return u < -0.55 ? "w1" : "w0";      }      return null;    });    // brass inlay ring set into the top face (near arc bright, far arc dull), then countable grain    for (let i = 0; i < 48; i++) {      const a = (i / 48) * TAU, x = Math.floor(SX + Math.cos(a) * 7), y = Math.floor(yt - 0.5 + Math.sin(a) * 1.7);      g.pixel(x, y, Math.sin(a) > 0 && Math.cos(a) < 0 ? "g1" : "g0");    }    g.line([SX - 7, 54], [SX - 4, 54], "w2"); g.line([SX + 3, 56], [SX + 6, 56], "w0");  });}function column(g) {  g.part("column", () => {    g.rect({ x: [SX - 1, SX], y: [SY + SUN_R - 1, 42] }, "g1", { fill: "g1" });    g.line([SX - 1, SY + SUN_R - 1], [SX - 1, 42], "g2");  });  g.part("hub", () => {    // clockwork drum: lit top face, toothed side, one arm per tier    g.rect({ x: [SX - 3, SX + 2], y: [HUB.top + 1, HUB.bot] }, "g1", { fill: "g1" });    g.line([SX - 3, HUB.top + 1], [SX - 3, HUB.bot], "g2");    g.line([SX + 2, HUB.top + 1], [SX + 2, HUB.bot], "g0");    g.line([SX - 2, HUB.top], [SX + 1, HUB.top], "g3"); g.pixel(SX + 1, HUB.top, "g2");    for (let x = SX - 3; x <= SX + 2; x += 2) g.pixel(x, HUB.bot, "g0");     // gear teeth    g.line([SX - 2, HUB.top + 2], [SX + 1, HUB.top + 2], "g0");            // seam between the tiers  });}// ---- sun -----------------------------------------------------------------------------------------------function sun(g, t) {  g.part("sun", () => {    g.field({ x: [SX - SUN_R, SX + SUN_R - 1], y: [SY - SUN_R, SY + SUN_R - 1] }, (x, y) => {      const nx = (x + 0.5 - SX) / SUN_R, ny = (y + 0.5 - SY) / SUN_R, r = Math.hypot(nx, ny);      if (r > 1) return null;      if (r > 0.86) return "fl0";                                     // cooler limb      let v = 1 - Math.hypot(nx + 0.25, ny + 0.25) * 0.9;             // hot core, offset toward the key light      v += 0.05 * Math.sin(TAU * 2 * t);                               // the core breathes      let i = v > 0.6 ? 3 : v > 0.28 ? 2 : 1;      // two sunspots ride the surface as the sun turns (one turn per loop, half a turn apart)      for (const [lat, ph] of [[-0.3, 0], [0.28, 0.5]]) {        const lon = ((t + ph) % 1) * 2 - 1;                             // -1..1 across the face, then round the back        const sx = Math.sin((lon * Math.PI) / 2) * Math.sqrt(1 - lat * lat) * 0.8;        if (i === 2 && Math.abs(nx - sx) < 0.12 && Math.abs(ny - lat) < 0.1 && r < 0.8) i = 1;   // never on the core      }      return "fl" + i;    });  });  g.detached("corona", () => {    for (let k = 0; k < 8; k++) {      const a = (k / 8) * TAU + TAU / 16;      const len = Math.sin(TAU * (2 * t) + k * 1.9) > 0.1 ? 2 : 1;      for (let i = 0; i < len; i++) {        const d = SUN_R + 0.7 + i;        g.pixel(Math.floor(SX + Math.cos(a) * d), Math.floor(SY + Math.sin(a) * d), i === 0 ? "fl1" : "fl0");      }    }  });}// ---- rings, arms, planets ------------------------------------------------------------------------------function rings(g) {  RINGS.forEach((R, i) => {    g.part("ring" + (i + 1), () => {      for (const p of RING_PX[i]) {        const col = p.front > 0 ? (p.u < -0.35 ? "g3" : "g2") : (p.u < 0 ? "g1" : "g0");        g.depth(p.front > 0 ? -1 : 1, () => g.pixel(p.x, p.y, col));      }    });  });}// planet sprite as a pixel map: sun-lit sphere + optional flat ring, ink outline around the unionfunction planetMap(cx, cy, rad, ramp, extra) {  const m = new Map(), key = (x, y) => x + "," + y;  const lx = SX - cx, ly = SY - cy, L = Math.hypot(lx, ly) || 1;  const ux = (0.75 * lx) / L - 0.2, uy = (0.75 * ly) / L - 0.2, uz = 0.62;   // sun light + a little key light  const x0 = Math.floor(cx - rad - 6), x1 = Math.ceil(cx + rad + 6);  const y0 = Math.floor(cy - rad - 3), y1 = Math.ceil(cy + rad + 3);  const body = (x, y) => { const nx = (x + 0.5 - cx) / rad, ny = (y + 0.5 - cy) / rad; return nx * nx + ny * ny <= 1; };  if (extra && extra.back) extra.back(m, key);  for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) {    if (!body(x, y)) continue;    const nx = (x + 0.5 - cx) / rad, ny = (y + 0.5 - cy) / rad, nz = Math.sqrt(Math.max(0, 1 - nx * nx - ny * ny));    const d = nx * ux + ny * uy + nz * uz;    m.set(key(x, y), ramp[clamp(Math.floor(((d + 0.1) / 1.1) * 4), 0, 3)]);  }  if (extra && extra.front) extra.front(m, key, body);  const ring = [];  for (let y = y0; y <= y1; y++) for (let x = x0; x <= x1; x++) {    if (m.has(key(x, y))) continue;    if (m.has(key(x - 1, y)) || m.has(key(x + 1, y)) || m.has(key(x, y - 1)) || m.has(key(x, y + 1))) ring.push(key(x, y));  }  for (const k of ring) m.set(k, "ink");  return { m, box: { x: [x0, x1], y: [y0, y1] } };}const clamp = (v, a, b) => Math.max(a, Math.min(b, v));function planetExtras(name, cx, cy, t) {  if (name === "iron") return {    front: (m, key) => { const x = Math.floor(cx), y = Math.floor(cy); m.set(key(x - 1, y), "n1"); m.set(key(x + 1, y + 1), "n2"); },  };  if (name === "blue") return {    // a small green continent that drifts round as the planet spins (2 turns per loop)    front: (m, key, body) => {      const x = Math.floor(cx), y = Math.floor(cy), s = Math.round(Math.sin(TAU * 2 * t) * 1.5);      for (const [dx, dy, c] of [[-1, -1, "e2"], [0, -1, "e1"], [-1, 0, "e1"], [1, 1, "e1"]]) {        const px = x + dx + s;        if (body(px, y + dy) && Math.abs(px + 0.5 - cx) < 3) m.set(key(px, y + dy), c);      }    },  };  // violet: a flat ring (11x3), far arc behind the ball, near arc in front  const pts = [];  for (let i = 0; i < 64; i++) {    const a = (i / 64) * TAU;    pts.push([Math.floor(cx + Math.cos(a) * 5.4), Math.floor(cy + Math.sin(a) * 1.4 - Math.cos(a) * 0.9), Math.sin(a)]);  }  return {    back: (m, key) => { for (const [x, y, s] of pts) if (s <= 0) m.set(key(x, y), x < cx ? "n4" : "n3"); },    front: (m, key) => { for (const [x, y, s] of pts) if (s > 0) m.set(key(x, y), x < cx ? "n5" : "n4"); },  };}function orbiters(g, t) {  RINGS.forEach((R) => {    const p = orbitPt(R, TAU * (R.ph + R.speed * t));    const cx = Math.floor(p.x) + 0.5, cy = Math.floor(p.y) + 0.5;    const near = p.front > 0;    // arm: from the hub drum on the column out to the planet (all arms share the drum's pivot, so they fan out    // from one point instead of running side by side)    g.depth(near ? -1 : 1, () => g.part("arm-" + R.name, () => {      g.line([SX, R.hub], [Math.floor(cx), Math.floor(cy)], near ? "g1" : "g0");    }));    const { m, box } = planetMap(cx, cy, R.rad, R.name === "iron" ? ["n1", "n2", "n3", "n4"] : R.name === "blue" ? ["b0", "b1", "b2", "b3"] : ["v0", "v1", "v2", "v3"], planetExtras(R.name, cx, cy, t));    g.depth(near ? -2 : 1, () => g.part(R.name, () => g.field(box, (x, y) => m.get(x + "," + y) ?? null)));  });}const Orrery = sprite(pal, (g, t) => {  contactShadow(g, SX, GROUND, 14, 2);  g.outlined("ink", () => {    pedestal(g);    column(g);  });  sun(g, t);  rings(g);  orbiters(g, t);});submit(Orrery, {  size: [48, 64], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 2.4, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    { name: "idle" },    { name: "idle-soul", repalette: swap("fl", "soul") },  ],});

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.