Tessarune
Dungeon Mechanisms · pressure_plate.js

Pressure plate

32×32 · 247 lines of JavaScript

A 32×32 floor trap: a dressed-stone frame of four mitred slabs around a socket, and a darker slate plate sitting 2px proud of the frame, carved with a rune (a ring round an algiz stroke). “up” (8 frames) pulses the rune’s grooves faintly; “press” (6 frames, once) sinks the plate flush, squirts dust from the gaps and flares the rune into a pillar of light; “down” (8 frames) keeps the rune glowing as a glint runs round the ring and motes rise; “release” (6 frames, once) springs the plate back up. “down-violet” swaps the glow to violet.

Pressure plate — Rendered output6×
Rendered by this program, variant “press” · 32×32 · 6 frames
Dungeon MechanismsGet the animated sprites: Dungeon Mechanisms on itch.ioVol.2 · 12 traps, doors and breakables

The header. The comment at the top of the file is the brief: size, view, light direction and every variant.

pressure_plate.js · 1–10
// Pressure plate — 32x32 floor trap, top-down 3/4, key light top-left.// A dressed-stone frame (four mitred slabs) around a socket; a darker slate plate sits in the socket, 2px proud of// the frame, with a carved rune (a ring round an algiz stroke). Stepping on it sinks the plate flush: the plate's// front face disappears, its top drops 2px and the dark back gap of the socket opens behind it.// Variants://   up       loop 8f     — plate raised, the rune's grooves faintly pulse//   press    one-shot 6f — plate sinks 2px, dust squirts from the gaps, the rune flares (light pillar) -> == down f0//   down     loop 8f     — plate flush, rune glowing steadily, a glint runs round the ring, motes rise//   release  one-shot 6f — rune dims, plate springs up 1px past rest and settles -> == up f0//   down-violet          — the glow ramp swapped to violet

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

pressure_plate.js · 236–247
submit(Plate, {  size: [32, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 0.8, loop: true },  anchor: { x: 16, y: GROUND },  variants: [    { name: "up", args: ["up"] },    { name: "press", args: ["press"], animate: { fps: 10, duration: 0.6, loop: false } },    { name: "down", args: ["down"] },    { name: "release", args: ["release"], animate: { fps: 10, duration: 0.6, loop: false } },    { name: "down-violet", args: ["down"], repalette: swap("glow", "v") },  ],});
Show the whole program (247 lines)
pressure_plate.js · 247 lines
// Pressure plate — 32x32 floor trap, top-down 3/4, key light top-left.// A dressed-stone frame (four mitred slabs) around a socket; a darker slate plate sits in the socket, 2px proud of// the frame, with a carved rune (a ring round an algiz stroke). Stepping on it sinks the plate flush: the plate's// front face disappears, its top drops 2px and the dark back gap of the socket opens behind it.// Variants://   up       loop 8f     — plate raised, the rune's grooves faintly pulse//   press    one-shot 6f — plate sinks 2px, dust squirts from the gaps, the rune flares (light pillar) -> == down f0//   down     loop 8f     — plate flush, rune glowing steadily, a glint runs round the ring, motes rise//   release  one-shot 6f — rune dims, plate springs up 1px past rest and settles -> == up f0//   down-violet          — the glow ramp swapped to violetconst W = 32, GROUND = 29;const FR = { x0: 1, x1: 30, y0: 6, y1: 26, face: 2 };   // frame top face rows y0..y1, front face below itconst OP = { x0: 5, x1: 26, y0: 9, y1: 23 };            // socket opening on the frame's top faceconst PL = { x0: 6, x1: 25, y0: 10, y1: 23 };           // plate footprint (flush position)const RY = 2;                                              // rune row offset inside the plate's top face// the rune: o = ring, a = diamond, * = core   (18 x 9 grid; symmetric about the plate centre)const RUNE = [  "......oooooo......",  "....oo......oo....",  "...o....aa....o...",  "..o....a**a....o..",  "o.o...a****a...o.o",  "..o....a**a....o..",  "...o....aa....o...",  "....oo......oo....",  "......oooooo......",];const GY = 0;                                              // grid row offset of RUNE[0]const RING = [];                                           // ring pixels in angular order, for the glint{  RUNE.forEach((row, y) => [...row].forEach((c, x) => { if (c === "o") RING.push([x, y, Math.atan2((y - 4) * 1.6, x - 8.5)]); }));  RING.sort((a, b) => a[2] - b[2]);}// ---- one frame into a key buffer: {part, key}function build(h, rune) {  const B = new Array(W * W).fill(null);  const set = (x, y, part, key) => { if (x >= 0 && y >= 0 && x < W && y < W) B[y * W + x] = { part, key }; };  // frame: a ring of dressed floor stones, top face then a 2px front face  for (let y = FR.y0; y <= FR.y1; y++) for (let x = FR.x0; x <= FR.x1; x++) {    if ((x === FR.x0 || x === FR.x1) && y === FR.y0) continue;                 // chamfered back corners    if (x >= OP.x0 && x <= OP.x1 && y >= OP.y0 && y <= OP.y1) {      // the socket: its back wall (faces the camera) is a dim row, the rest a black gap      set(x, y, "socket", y === OP.y0 ? "n0" : "ink");      continue;    }    let k = "n2";    if (y === FR.y0 || x === FR.x0 || (y === FR.y0 + 1 && (x === FR.x0 + 1))) k = "n3";   // outer north / west edges lit    if (x === FR.x1 && y > FR.y0) k = "n1";                                               // outer east edge    if (y === OP.y1 + 1 && x >= OP.x0 && x <= OP.x1) k = "n3";                           // front rim's inner lip faces north    if (x === OP.x1 + 1 && y >= OP.y0 && y <= OP.y1) k = "n3";                           // right rim's inner lip faces west    if (y === OP.y0 - 1 && x >= OP.x0 - 1 && x <= OP.x1) k = "n1";                       // back rim's inner lip faces south    if (x === OP.x0 - 1 && y >= OP.y0 - 1 && y <= OP.y1) k = "n1";                       // left rim's inner lip faces east    set(x, y, "frame", k);  }  // block joints across the rims (dark joint, the next stone's lit edge beside it)  const jointV = (x, y0, y1) => { for (let y = y0; y <= y1; y++) { set(x, y, "frame", "n1"); if (B[y * W + x + 1] && B[y * W + x + 1].key === "n2") set(x + 1, y, "frame", "n3"); } };  const jointH = (y, x0, x1) => { for (let x = x0; x <= x1; x++) { set(x, y, "frame", "n1"); if (B[(y + 1) * W + x] && B[(y + 1) * W + x].key === "n2") set(x, y + 1, "frame", "n3"); } };  jointV(11, FR.y0 + 1, OP.y0 - 2); jointV(21, FR.y0 + 1, OP.y0 - 2);  jointV(9, OP.y1 + 2, FR.y1); jointV(18, OP.y1 + 2, FR.y1); jointV(25, OP.y1 + 2, FR.y1);  jointH(15, FR.x0 + 1, OP.x0 - 2); jointH(18, OP.x1 + 2, FR.x1 - 1);  // chips (countable marks)  set(3, 21, "frame", "n1"); set(4, 21, "frame", "n3");  set(28, 11, "frame", "n1"); set(28, 12, "frame", "n3");  set(14, 25, "frame", "n1"); set(15, 25, "frame", "n1");  // frame front face  for (let i = 1; i <= FR.face; i++) for (let x = FR.x0; x <= FR.x1; x++) {    if ((x === FR.x0 || x === FR.x1) && i === FR.face) continue;                 // chamfered front corners    let k = i === 1 ? "n1" : "n0";    if (x === FR.x0 + (i === FR.face ? 1 : 0)) k = i === 1 ? "n2" : "n1";    if (x === 9 || x === 18 || x === 25) k = "ink";                              // the joints run down the face    set(x, FR.y1 + i, "frame", k);  }  // plate: top face shifted up by h, front face h rows tall (inside the socket, so its sides never show)  const top = PL.y0 - h, bot = PL.y1 - h;  for (let y = top; y <= bot; y++) for (let x = PL.x0; x <= PL.x1; x++) {    let k = "n3";    if (x === PL.x1) k = "n2";    if (x === PL.x0 || y === top) k = "n4";    if (y === top && x === PL.x1) k = "n3";    // carved corner brackets one pixel in    const cx = x - PL.x0 <= 3 ? x - PL.x0 : PL.x1 - x <= 3 ? PL.x1 - x : -1;    const cy = y - top <= 3 ? y - top : bot - y <= 3 ? bot - y : -1;    if (cx >= 1 && cy >= 1 && ((cx === 1 && cy <= 3) || (cy === 1 && cx <= 3))) k = "n2";    set(x, y, "plate", k);  }  for (let i = 1; i <= h; i++) for (let x = PL.x0; x <= PL.x1; x++) {    set(x, bot + i, "plate", i === 1 ? (x === PL.x0 ? "n2" : "n1") : x === PL.x0 ? "n1" : "n0");  }  // rune grooves, then the groove's lower wall: lit stone when dark, a dim glow when the rune is lit  const RW = RUNE[0].length, RH = RUNE.length;  const isG = (x, y) => y >= 0 && y < RH && x >= 0 && x < RW && RUNE[y][x] !== ".";  const inside = (x, y) => Math.abs(x - 8.5) + Math.abs(y - 4) * 1.6 < 5.5;   // the diamond and its surround  for (let y = 0; y < RH; y++) for (let x = 0; x < RW; x++) {    const px = PL.x0 + 1 + x, py = top + RY + GY + y;    const c = RUNE[y][x];    if (c !== ".") { set(px, py, "rune", rune.key(c, x, y)); continue; }    const adj = isG(x - 1, y) || isG(x + 1, y) || isG(x, y - 1) || isG(x, y + 1);    if (rune.halo === "all" && adj) set(px, py, "rune", rune.haloKey);    else if (isG(x, y - 1) && !inside(x, y)) {      if (rune.halo) set(px, py, "rune", rune.haloKey); else set(px, py, "plate", "n4");    }  }  return B;}const PARTS = ["frame", "socket", "plate", "rune"];function draw(g, B) {  g.outlined("ink", () => {    for (const part of PARTS) g.part(part, () => g.field({ x: [0, W - 1], y: [0, W - 1] }, (x, y) => {      const b = B[y * W + x];      return b && b.part === part ? b.key : null;    }));  });}// ---- rune looksconst lvKey = (lv) => ["glow0", "glow1", "glow2", "glow3"][Math.max(0, Math.min(3, lv))];// a look = levels for ring / diamond / core; halo: null (dark grooves), "low" (dim glow on the grooves' lower wall)// or "all" (a solid glow around every groove, for the flash)function look(ring, dia, core, halo, haloLv, glint) {  return {    key: (c, x, y) => (c === "o" && glint && glint(x, y) ? "glow3" : lvKey(c === "o" ? ring : c === "a" ? dia : core)),    halo: halo || null, haloKey: lvKey(haloLv || 0),  };}function runeUp(t) {  // faint pulse: dark grooves; the core warms first, then the diamond  const p = 0.5 - 0.5 * Math.cos(TAU * t);  return look(0, p > 0.6 ? 1 : 0, p > 0.85 ? 2 : p > 0.3 ? 1 : 0);}function runeDown(t) {  // steady: ring and diamond glow2, core white-hot, ticks glow1; a glint runs round the ring  const i = Math.floor(t * RING.length) % RING.length;  const on = new Set([i, (i + 1) % RING.length].map((k) => RING[k][0] + "," + RING[k][1]));  return look(2, 2, 3, "low", 1, (x, y) => on.has(x + "," + y));}// ---- effectsfunction pillar(g, top, amount) {  // a shaft of light straight up out of the rune: solid at the flash, thinning into dither as it fades  if (amount <= 0) return;  g.detached("flare", () => {    const base = top + RY + 4;    const cols = [[13, 0.5, "glow2"], [14, 0.8, "glow2"], [15, 1, "glow3"], [16, 1, "glow3"], [17, 0.8, "glow2"], [18, 0.5, "glow2"]];    for (const [x, s, k0] of cols) {      const len = Math.round(amount * s * (base + 1));      for (let k = 0; k < len; k++) {        const y = base - k;        if (y < 0) break;        const u = k / len;        if ((amount < 0.9 || u > 0.55 || s < 0.6) && !chk(x, y)) continue;   // the fringe is always dithered        g.pixel(x, y, u > 0.8 ? "glow1" : u > 0.55 && k0 === "glow3" ? "glow2" : k0);      }    }    // sparks flung sideways from the flash    if (amount > 0.5) for (const [dx, dy] of [[-6, -4], [7, -3], [-9, 1], [10, 2]]) {      const x = 15.5 + dx * (amount > 0.9 ? 1 : 1.4), y = base - 3 + dy * (amount > 0.9 ? 1 : 1.4) - (amount > 0.9 ? 0 : 2);      g.pixel(Math.round(x), Math.round(y), amount > 0.9 ? "glow3" : "glow2");    }  });}// dust: [x, y, dx, dy] — puffs squirting out of the side gaps and over the front rimconst DUST = [[3, 13, -1, -0.6], [4, 20, -1, 0.2], [28, 13, 1, -0.6], [27, 20, 1, 0.2], [10, 25, -0.8, 0.3], [21, 25, 0.8, 0.3]];function dust(g, k) {  if (k <= 0 || k > 3) return;  g.detached("dust", () => {    for (const [x0, y0, dx, dy] of DUST) {      const x = Math.max(1, Math.min(30, Math.round(x0 + dx * (k - 1) * 1.3))), y = Math.round(y0 + dy * (k - 1) * 1.5 - (k - 1) * 0.7);      const s = dx < 0 ? -1 : 1;      if (k === 1) {                 // a round puff: lit crown, grey body, shaded underside        g.pixel(x, y - 1, "n5"); g.pixel(x + s, y - 1, "n4"); g.pixel(x, y, "n4"); g.pixel(x + s, y, "n3");      } else if (k === 2) {          // it spreads out and thins        g.pixel(x - 1, y, "n4"); g.pixel(x, y, "n4"); g.pixel(x + 1, y, "n3"); g.pixel(x + s, y - 1, "n4");      } else {                       // last wisps        g.pixel(x, y - 1, "n3"); if (x + 2 * s >= 0 && x + 2 * s <= 31) g.pixel(x + 2 * s, y, "n3");      }    }  });}function motes(g, top, t) {  g.detached("motes", () => {    for (let k = 0; k < 3; k++) {      const p = (t + k / 3) % 1;      const x = [12, 16, 19][k] + Math.round(Math.sin(TAU * (p + k * 0.37)));      const y = top + RY + 3 - Math.round(p * 11);      if (p < 0.8) g.pixel(x, y, p < 0.4 ? "glow3" : "glow2");    }  });}// ---- statesconst PRESS = [  // h, rune, pillar, dust stage  [2, () => runeUp(0), 0, 0],  [1, () => look(1, 1, 2, "low", 0), 0, 0],  [0, () => look(3, 3, 3, "all", 2), 1, 1],    // impact: the rune flares white, dust squirts from the gaps  [0, () => look(3, 3, 3, "all", 1), 0.7, 2],  [0, () => look(2, 3, 3, "low", 1), 0.35, 3],  [0, () => runeDown(0), 0, 0],                // == down, frame 0];const RELEASE = [  [0, () => runeDown(0)],                      // == down, frame 0  [0, () => look(1, 2, 2, "low", 0)],  [1, () => look(1, 1, 1)],  [3, () => look(0, 0, 1)],             // springs up a pixel past rest  [2, () => runeUp(0)],  [2, () => runeUp(0)],                        // == up, frame 0];const Plate = sprite(pal, (g, t, state) => {  contactShadow(g, 16, GROUND, 15, 2);  if (state === "up") { draw(g, build(2, runeUp(t))); return; }  if (state === "down") {    draw(g, build(0, runeDown(t)));    motes(g, PL.y0, t);    return;  }  if (state === "press") {    const f = Math.min(PRESS.length - 1, Math.round(t * PRESS.length));    const [h, rune, amount, d] = PRESS[f];    draw(g, build(h, rune()));    pillar(g, PL.y0 - h, amount);    dust(g, d);    if (f === PRESS.length - 1) motes(g, PL.y0, 0);    return;  }  const f = Math.min(RELEASE.length - 1, Math.round(t * RELEASE.length));  const [h, rune] = RELEASE[f];  draw(g, build(h, rune()));  if (f === 0) motes(g, PL.y0, 0);});submit(Plate, {  size: [32, 32], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: 0.8, loop: true },  anchor: { x: 16, y: GROUND },  variants: [    { name: "up", args: ["up"] },    { name: "press", args: ["press"], animate: { fps: 10, duration: 0.6, loop: false } },    { name: "down", args: ["down"] },    { name: "release", args: ["release"], animate: { fps: 10, duration: 0.6, loop: false } },    { name: "down-violet", args: ["down"], repalette: swap("glow", "v") },  ],});

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.