Tessarune
Harvest Festival · cider_press.js

シードルの搾り機

48×64 · JavaScript 403 行

縁の上がった厚い台座と 2 本の支柱、砕いたリンゴを詰めた籠、木製の桶、梁の上の赤いリンゴを備えた、48×64 の木製スクリュー式圧搾機です。16 コマの idle(待機)では注ぎ口から果汁の滴が桶へと落ちて波紋を作り、果肉が溝を滑り落ちます。16 コマの press ではバーが半回転して果汁が流れ出し、12 コマの full では泡立つ桶の周りをハチが飛び回ります。

シードルの搾り機 — 描画結果6×
このプログラムで描画(変種「press」) · 48×64 · 16 コマ
Harvest Festival動くスプライト素材: Harvest Festival(itch.io)Vol.6・収穫祭の小物 12 点・37 アニメ

冒頭のコメント。これが指示書です。サイズ、視点、光の向き、すべての変種が書かれています。

cider_press.js · 1–12
// Cider press — 48x64 floor prop (tall), top-down 3/4, key light top-left.// A wooden screw press: a thick plank bed with a raised rim, two posts carrying a crossbeam, a big threaded wooden// screw through the beam with a hub and a long turning bar on top, a pressing block and round plate bearing down on a// slatted basket (two iron hoops) full of crushed apples. Cider runs across the bed into a carved spout and pours into// a staved wooden bucket standing in front. A red apple sits on the end of the beam.// Variants://   idle   16f loop  a drop of cider swells at the spout and falls into the bucket (ring on the surface), a bit of//                    apple pulp slides down the spout channel//   press  16f loop  the bar turns half a revolution around the screw (across, angled, end-on toward the viewer,//                    angled back), the threads wind down, the plate sits 2px lower in the basket, pomace squeezes up,//                    juice runs down between the slats and streams steadily from the spout//   full   12f loop  the bucket is brimming, a head of froth heaves and pops, a slow drip, a wasp buzzes around it

最後の数行。submit でキャンバスの大きさと、描画エンジンが書き出す変種の一覧を指定します。

cider_press.js · 393–403
submit(CiderPress, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "press", args: ["press"], animate: { fps: 10, duration: N_PRESS / 10, loop: true } },    { name: "full", args: ["full"], animate: { fps: 10, duration: N_FULL / 10, loop: true } },  ],});
プログラム全体を表示 (403 行)
cider_press.js · 403 行
// Cider press — 48x64 floor prop (tall), top-down 3/4, key light top-left.// A wooden screw press: a thick plank bed with a raised rim, two posts carrying a crossbeam, a big threaded wooden// screw through the beam with a hub and a long turning bar on top, a pressing block and round plate bearing down on a// slatted basket (two iron hoops) full of crushed apples. Cider runs across the bed into a carved spout and pours into// a staved wooden bucket standing in front. A red apple sits on the end of the beam.// Variants://   idle   16f loop  a drop of cider swells at the spout and falls into the bucket (ring on the surface), a bit of//                    apple pulp slides down the spout channel//   press  16f loop  the bar turns half a revolution around the screw (across, angled, end-on toward the viewer,//                    angled back), the threads wind down, the plate sits 2px lower in the basket, pomace squeezes up,//                    juice runs down between the slats and streams steadily from the spout//   full   12f loop  the bucket is brimming, a head of froth heaves and pops, a slow drip, a wasp buzzes around itconst W = 48, H = 64, GROUND = 60, CX = 24;const N_IDLE = 16, N_PRESS = 16, N_FULL = 12;const BX = 18;                                // press axis (screw, plate, basket centre)const BRX = 10, BTY = 34, BBY = 48, BRY = 3;  // basketconst HUB_Y = 6;                              // bar axis rowconst BKX = 41, BKCY = 54;                    // bucket centre (rim ellipse)const e2 = (x, y, cx, cy, rx, ry) => ((x + 0.5 - cx) / rx) ** 2 + ((y + 0.5 - cy) / ry) ** 2;const cl = (v, a, b) => Math.max(a, Math.min(b, v));const WOOD = ["w0", "w1", "w2", "w3", "w4"], IRON = ["n0", "n1", "n2", "n3", "n4"];// a pixel map ringed with ink by hand (outlined() only inks empty pixels and these parts overlap each other)function stamp(g, name, m, opts = {}) {  const ink = new Set();  for (const i of m.keys()) {    const x = i % W, y = (i - x) / W;    for (const [nx, ny] of [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]]) {      if (nx < 0 || ny < 0 || nx >= W || ny >= H) continue;      const k = ny * W + nx;      if (!m.has(k) && !(opts.skip && opts.skip(nx, ny))) ink.add(k);    }  }  let x0 = W, x1 = 0, y0 = H, y1 = 0;  for (const i of [...m.keys(), ...ink]) { const x = i % W, y = (i - x) / W; x0 = Math.min(x0, x); x1 = Math.max(x1, x); y0 = Math.min(y0, y); y1 = Math.max(y1, y); }  if (x0 > x1) return;  g.part(name, () => g.field({ x: [x0, x1], y: [y0, y1] }, (x, y) => {    const i = y * W + x;    return m.has(i) ? m.get(i) : ink.has(i) ? "ink" : null;  }));}const pmap = () => { const m = new Map(); m.P = (x, y, k) => { if (x >= 0 && y >= 0 && x < W && y < H) m.set(y * W + x, k); }; return m; };// ---- bed (tray) ------------------------------------------------------------------------------------------------const TX0 = 2, TX1 = 34, TTOP = 44, TFRONT = 52, TBOT = 59;function bed(g, S) {  g.part("bed", () => g.field({ x: [TX0, TX1], y: [TTOP, TBOT] }, (x, y) => {    if (y <= TFRONT) {                                              // top face: rim around a recessed floor      if (y === TTOP) return x < 12 ? "w4" : "w3";      if (x === TX0) return "w4";      if (x === TX1) return "w2";      if (y === TFRONT) return x < 8 ? "w4" : "w3";      if (y === TTOP + 1) return "w1";                              // far inner wall in shadow      if (x === TX0 + 1) return "w1";      // floor: juice film collects toward the spout corner (right), more of it while pressing      const wet = S.mode === "idle" ? 0.55 : 0.25;      if ((x - TX0) / (TX1 - TX0) > wet + (TFRONT - y) * 0.03) return y === TTOP + 2 ? "g0" : (x + y) % 7 === 0 ? "g2" : "g1";      return (x * 3 + y) % 11 === 0 ? "w1" : "w2";    }    // front face: two planks, lit left end, darker toward the floor    if (x === TX0) return y === TBOT ? "w2" : "w3";    if (x === TX1) return "w0";    if (y === TFRONT + 1) return "w3";    if (y === 56) return "w1";                                      // plank seam    if (y === TBOT) return "w1";    if ((y === 54 && x % 9 === 4) || (y === 58 && x % 7 === 2)) return "w1";   // grain    return "w2";  }));  g.part("bedIron", () => {                                          // corner straps with nails    for (const x of [TX0 + 1, TX1 - 2]) { g.pixel(x, 54, "n2"); g.pixel(x + 1, 54, "n1"); g.pixel(x, 57, "n2"); g.pixel(x + 1, 57, "n1"); }    g.pixel(TX0 + 1, 54, "n4"); g.pixel(TX0 + 1, 57, "n3");  });  g.part("pulp", () => {                                             // spilled pomace on the bed rim and floor    for (const [x, y, k] of [[4, 50, "r1"], [5, 50, "g1"], [31, 51, "e2"], [32, 50, "r1"], [6, TFRONT, "r1"], [7, TFRONT, "g1"], [25, TFRONT, "e1"]]) g.pixel(x, y, k);  });}function spout(g, S, t) {  const m = pmap();  for (let x = 35; x <= 39; x++) {    m.P(x, 46, x === 35 ? "w4" : "w3");                               // lit lip    const flow = S.mode === "press" ? ((x - Math.floor(t * N_PRESS * 1.5)) % 3 + 3) % 3 : ((x - Math.floor(t * 8)) % 4 + 4) % 4;    m.P(x, 47, flow === 0 ? "g2" : "g1");                             // cider in the channel    m.P(x, 48, "w1");  }  m.P(40, 47, "w2"); m.P(40, 48, "w0");                                // the tip  if (S.mode === "idle") {                                            // a bit of pulp slides down the channel    const p = t;    if (p < 0.6) m.P(35 + Math.floor(p / 0.6 * 5), 47, "r1");  }  stamp(g, "spout", m, { skip: (x) => x <= TX1 });}// ---- posts, beam, screw, bar ---------------------------------------------------------------------------------function frame(g) {  g.part("posts", () => {    for (const px of [4, 30]) {      for (let y = 16; y <= 47; y++) {        g.pixel(px, y, "w3"); g.pixel(px + 1, y, "w2"); g.pixel(px + 2, y, "w1");      }      g.pixel(px + 1, 22, "w1"); g.pixel(px + 1, 23, "w1"); g.pixel(px, 31, "w4"); g.pixel(px + 1, 39, "w1");   // grain, a lit knot      g.line([px, 16], [px + 2, 16], "w0");                               // under the beam      g.line([px, 47], [px + 2, 47], "w0");                               // foot in its mortise      g.pixel(px + 1, 18, "w4"); g.pixel(px + 2, 18, "w2");               // through-peg    }  });  g.part("beam", () => g.field({ x: [1, 35], y: [10, 15] }, (x, y) => {    if (x === 1) return y <= 11 ? "w4" : y === 15 ? "w2" : "w3";          // end grain, lit    if (x === 35) return y <= 11 ? "w2" : "w0";    if (y === 10) return x < 14 ? "w4" : "w3";    if (y === 11) return "w3";    if (y === 12) return x < 10 ? "w4" : "w3";    if (y === 15) return "w1";    if ((y === 13 && x % 8 === 3) || (y === 14 && x % 11 === 6)) return "w1";    return "w2";  }));  g.part("beamHole", () => { g.pixel(BX - 2, 10, "w0"); g.pixel(BX + 2, 11, "w1"); g.pixel(BX - 2, 11, "w0"); g.pixel(BX + 2, 10, "w1"); });}function screw(g, y0, y1, shift) {  // 3px threaded shaft: lit left column, threads as a diagonal helix that winds down with `shift`  g.part("screw", () => {    for (let y = y0; y <= y1; y++) for (let c = 0; c < 3; c++) {      const lv = [3, 2, 1][c];      const th = ((y - shift - c) % 3 + 3) % 3;      let k = th === 0 ? lv - 1 : th === 1 ? lv + 1 : lv;      if (y === y0 && y0 > 12) k = 0;                                      // shadow under the beam      g.pixel(BX - 1 + c, y, WOOD[cl(k, 0, 4)]);    }  });}function plateAndBlock(g, py) {  // round plate (top face + 2px thickness) with a pressing block on it  g.part("plate", () => g.field({ x: [BX - 7, BX + 7], y: [py - 3, py + 4] }, (x, y) => {    const top = e2(x, y, BX, py, 7, 2.2) <= 1;    if (top) {      const nx = (x + 0.5 - BX) / 7, ny = (y + 0.5 - py) / 2.2;      const a = -0.7 * nx - 0.5 * ny;      if (e2(x, y - 1, BX, py, 7, 2.2) > 1 && nx < 0.2) return "w4";      return a > 0.25 ? "w4" : a > -0.35 ? "w3" : "w2";    }    for (const d of [1, 2]) if (e2(x, y - d, BX, py, 7, 2.2) <= 1) {      const nx = (x + 0.5 - BX) / 7;      return d === 2 ? "w0" : nx < -0.4 ? "w2" : "w1";    }    return null;  }));  g.part("block", () => {    const yt = py - 4;    for (let x = BX - 3; x <= BX + 3; x++) {      g.pixel(x, yt, x < BX ? "w4" : "w3");      for (let y = yt + 1; y <= py; y++) g.pixel(x, y, x === BX - 3 ? "w3" : x === BX + 3 ? "w1" : y === py ? "w1" : "w2");    }    g.pixel(BX + 1, yt + 2, "w1"); g.pixel(BX - 1, yt + 3, "w1");          // grain  });}function hub(g) {  const m = pmap();  for (let x = BX - 3; x <= BX + 3; x++) {    m.P(x, 3, x < BX ? "w4" : "w3"); m.P(x, 4, x < BX + 1 ? "w3" : "w2");    for (let y = 5; y <= 8; y++) m.P(x, y, x === BX - 3 ? "w3" : x >= BX + 2 ? "w1" : y === 8 ? "w1" : "w2");  }  m.P(BX - 3, 3, "w3"); m.P(BX + 3, 3, "w2");  for (let y = 9; y <= 9; y++) for (let c = 0; c < 3; c++) m.P(BX - 1 + c, y, ["w2", "w1", "w0"][c]);   // collar  stamp(g, "hub", m);}function barHalf(g, th, sgn, near) {  // one half of the bar, from the hub axis out to its end; th in degrees (0 = across the view)  const L = 13, a = (th * Math.PI) / 180;  const ex = BX + sgn * L * Math.cos(a), ey = HUB_Y + sgn * L * 0.48 * Math.sin(a);  const steep = Math.abs(Math.cos(a)) < 0.45;  const m = pmap();  // the bar passes through the hub: the near half emerges from the hub's faces, it never paints across them  if (near) { const P0 = m.P; m.P = (x, y, k) => { if (!(x >= BX - 3 && x <= BX + 3 && y >= 3 && y <= 9)) P0(x, y, k); }; }  const n = Math.ceil(Math.max(Math.abs(ex - BX), Math.abs(ey - HUB_Y)));  for (let i = 0; i <= n; i++) {    const x = Math.round(BX + ((ex - BX) * i) / n), y = Math.round(HUB_Y + ((ey - HUB_Y) * i) / n);    if (steep) { m.P(x, y, near ? "w3" : "w2"); m.P(x + 1, y, near ? "w2" : "w1"); }    else { m.P(x, y, near ? "w3" : "w2"); m.P(x, y + 1, near ? "w1" : "w0"); }  }  // the end: lit end grain on the near end, a worn grip  const xe = Math.round(ex), ye = Math.round(ey);  if (near) { m.P(xe, ye, "w4"); if (steep) m.P(xe, ye + 1, "w3"); }  stamp(g, sgn > 0 ? "barR" : "barL", m);}function bar(g, th) {  const s = Math.sin((th * Math.PI) / 180);  if (Math.abs(s) < 0.2) { barHalf(g, th, -1, true); barHalf(g, th, 1, false); hub(g); return; }  const nearSgn = s > 0 ? 1 : -1;  barHalf(g, th, -nearSgn, false);  hub(g);  barHalf(g, th, nearSgn, true);}function beamApple(g) {  const m = pmap();  const R = [[1, 3], [0, 4], [0, 4], [1, 3]];  R.forEach(([a, b], r) => { for (let c = a; c <= b; c++) { const i = litIndex(c, r, 2.5, 2, 2.6, 2.2, 3, 0); m.P(3 + c, 6 + r, ["r0", "r1", "r2"][Math.max(0, i)]); } });  m.P(4, 7, "r3"); m.P(4, 6, "n5"); m.P(5, 5, "w1"); m.P(6, 5, "e2");  stamp(g, "apple", m);}// ---- basket ------------------------------------------------------------------------------------------------------const topFront = (x) => BTY + BRY * Math.sqrt(Math.max(0, 1 - ((x + 0.5 - BX) / BRX) ** 2));function basket(g, S, t, py) {  const press = S.mode === "press";  const tick = Math.floor(t * N_PRESS);  g.part("basket", () => g.field({ x: [BX - BRX, BX + BRX], y: [BTY - BRY, BBY + BRY] }, (x, y) => {    const nx = (x + 0.5 - BX) / BRX;    if (Math.abs(nx) > 1) return null;    const outerTop = e2(x, y, BX, BTY, BRX, BRY) <= 0.99;    const innerTop = e2(x, y, BX, BTY, BRX - 1.3, BRY - 1) <= 0.99;    const tf = topFront(x);    if (outerTop && !innerTop) {                                       // slat ends (rim ring)      const ny = (y + 0.5 - BTY) / BRY;      if (ny < 0) return nx < 0.3 ? "w3" : "w2";      return nx < -0.2 ? "w4" : nx < 0.5 ? "w3" : "w2";    }    if (innerTop) {                                                    // inside: pomace around the plate      const plateHere = e2(x, y, BX, py, 7, 2.2) <= 1 || e2(x, y - 1, BX, py, 7, 2.2) <= 1 || e2(x, y - 2, BX, py, 7, 2.2) <= 1;      if (plateHere) return null;      const h = hash(x, y);      if (press && h < 0.3) return "g2";                               // juice welling up      return h < 0.18 ? "r1" : h < 0.32 ? "e2" : h < 0.45 ? "w2" : h < 0.6 ? "g0" : "g1";    }    const bottom = BBY + BRY * Math.sqrt(Math.max(0, 1 - nx * nx));    if (y <= tf || y > bottom) return null;    const dy = y - Math.floor(tf);    // slats: 2px slat, 1px gap, laid out along the arc so they crowd toward the sides    const arc = Math.asin(cl(nx, -0.999, 0.999)) * BRX + 30;    const gap = arc % 3 < 0.9;    const lv = nx < -0.6 ? 3 : nx < 0.1 ? 2 : nx < 0.65 ? 1 : 0;    if (dy === 2 || dy === 3 || y === Math.floor(bottom) - 2 || y === Math.floor(bottom) - 1) {   // iron hoops      const topRow = dy === 2 || y === Math.floor(bottom) - 2;      if (topRow && [-0.7, 0, 0.6].some((u) => Math.round(BX + u * BRX - 0.5) === x)) return lv >= 2 ? "n4" : "n3";      return IRON[cl(lv + (topRow ? 1 : 0), 0, 4)];    }    if (gap) {      if (press) { const b = ((y - tick) % 4 + 4) % 4; return b === 0 ? "g2" : b === 1 ? "g1" : "g0"; }   // juice running down between the slats      return dy < 6 && hash(x, 3) < 0.5 ? "r0" : "w0";    }    let k = lv + 1;    if (dy === 1) k -= 1;                                              // under the rim    if (y >= Math.floor(bottom)) k -= 1;    if (dy === 4) k -= 1;                                              // hoop shadow    return WOOD[cl(k, 0, 4)];  }));  if (press) g.part("ooze", () => {                                    // pomace squeezed up around the plate    for (const [dx, k] of [[-8, "r1"], [-7, "g1"], [7, "e2"], [8, "g2"]]) g.pixel(BX + dx, py - 1, k);    g.pixel(BX - 6, py - 2, "g2"); g.pixel(BX + 5, py - 2, "r1");  });}function basketDrips(g, t) {  // pressing: juice drips from under the basket onto the bed  g.detached("drips", () => {    for (const [x, ph] of [[11, 0], [16, 0.5], [24, 0.25]]) {      const p = (t * 2 + ph) % 1;      const y = 50 + Math.floor(p * 3);      if (p < 0.75) g.pixel(x, y, p < 0.35 ? "g2" : "g1");    }  });}// ---- bucket ------------------------------------------------------------------------------------------------------function bucket(g, S, t) {  const m = pmap();  const rx = 5;  for (let y = BKCY - 2; y <= 61; y++) for (let x = BKX - rx; x <= BKX + rx; x++) {    const nx = (x + 0.5 - BKX) / (rx + 0.5);    const inRim = e2(x, y, BKX, BKCY, rx + 0.5, 2.2) <= 1;    const inMouth = e2(x, y, BKX, BKCY, rx - 0.5, 1.4) <= 1;    if (inRim && !inMouth) {      const ny = (y + 0.5 - BKCY) / 2.2;      m.P(x, y, ny < 0 ? (nx < 0.2 ? "w3" : "w2") : nx < -0.2 ? "w4" : nx < 0.5 ? "w3" : "w2");      continue;    }    if (inMouth) {                                                       // cider: far strip in shade, glint      const far = e2(x, y - 1, BKX, BKCY, rx - 0.5, 1.4) > 1;      m.P(x, y, far ? "g0" : "g1");      continue;    }    if (y <= BKCY) continue;    const hw = y <= 58 ? rx : rx - 1;   // tapers toward the foot    if (Math.abs(x - BKX) > hw || (y === 61 && Math.abs(x - BKX) >= hw)) continue;    const u = (x - BKX) / hw;    if (y === 57 || y === 60) { m.P(x, y, u < -0.5 ? "n3" : u < 0.4 ? "n2" : "n1"); continue; }   // hoops    let k = u < -0.6 ? 3 : u < 0.2 ? 2 : u < 0.7 ? 1 : 0;    if ((x === BKX - 2 || x === BKX + 2) && k === 2) k = 1;              // stave seams    if (y === 61 || y === BKCY + 2 - 0) k -= 0;    if (y === 61) k -= 1;    m.P(x, y, WOOD[cl(k, 0, 4)]);  }  m.P(BKX - 2, BKCY, "g3"); m.P(BKX - 1, BKCY, "g2");                    // glint  if (S.mode === "full") {                                              // brimming: a head of froth over the rim    const heave = Math.round(Math.sin(TAU * t) * 0.6 + 0.4);    for (let x = BKX - 4; x <= BKX + 4; x++) {      const ht = Math.round(2.2 * Math.sqrt(Math.max(0, 1 - ((x - BKX) / 4.6) ** 2))) + (Math.abs(x - BKX) < 2 ? heave : 0);      for (let k = 0; k <= ht; k++) {        const y = BKCY + 1 - k;        m.P(x, y, k === ht ? (x < BKX + 2 ? "n5" : "g3") : x > BKX + 2 ? "g2" : k === 0 ? "g2" : "g3");      }    }    m.P(BKX + 4, BKCY + 2, "g3"); m.P(BKX + 4, BKCY + 3, "g3");   // froth spills over the lip    m.P(BKX + 4, BKCY + 4, "g2");  }  stamp(g, "bucket", m);  g.part("bucketBail", () => {                                         // rope handle ears    g.pixel(BKX - 5, BKCY + 2, "n2"); g.pixel(BKX + 5, BKCY + 2, "n1");  });}function froth(g, t) {  // bubbles on the head: rise, swell, pop  g.detached("bubbles", () => {    for (const [dx, dy, ph] of [[-2, -2, 0], [1, -3, 0.33], [3, -1, 0.66]]) {      const p = (t + ph) % 1, x = BKX + dx, y = BKCY + dy;      if (p < 0.4) g.pixel(x, y, "g3");      else if (p < 0.6) { g.pixel(x, y, "n5"); g.pixel(x, y - 1, "g3"); }      else if (p < 0.72) { g.pixel(x - 1, y - 1, "n5"); g.pixel(x + 1, y - 1, "n5"); }    }  });}function wasp(g, t) {  // gold-and-ink wasp on a figure-of-eight around the bucket; wings blur every frame  const f = Math.round(t * N_FULL) % N_FULL;  const x = BKX + Math.round(Math.sin(TAU * t) * 5), y = 44 + Math.round(Math.sin(2 * TAU * t) * 3) + Math.round(Math.cos(TAU * t) * 2);  const dir = Math.cos(TAU * t) >= 0 ? 1 : -1;  g.detached("wasp", () => {    g.pixel(x, y, "g2");                                                  // abdomen    g.pixel(x + dir, y, "ink");                                           // head    g.pixel(x - dir, y, f % 2 ? "ink" : "g1");                            // stinger stripe    if (f % 2 === 0) { g.pixel(x, y - 1, "n5"); g.pixel(x - dir, y - 1, "n4"); }    else { g.pixel(x, y - 1, "n4"); g.pixel(x + dir, y - 1, "n5"); }  });}// ---- cider in motion -------------------------------------------------------------------------------------------------const TIP = [40, 49], SURF = 54;function drip(g, t, n) {  // one drop per loop: swell at the tip, fall, ring on the surface  const f = Math.round(t * n) % n, fall = n - 6;  g.detached("drip", () => {    const [x, y] = TIP;    if (f < fall - 3) g.pixel(x, y, "g1");    else if (f < fall) { g.pixel(x, y, "g2"); g.pixel(x, y + 1, "g1"); }    else if (f === fall) { g.pixel(x, y + 1, "g2"); g.pixel(x, y + 2, "g3"); }    else if (f === fall + 1) { g.pixel(x, y + 3, "g3"); }    else if (f === fall + 2) { g.pixel(x - 1, SURF, "g3"); g.pixel(x + 1, SURF, "g3"); g.pixel(x, SURF - 1, "g2"); }    else if (f === fall + 3) { g.pixel(x - 2, SURF, "g2"); g.pixel(x + 2, SURF, "g2"); }  });}function stream(g, t) {  g.detached("stream", () => {    const tick = Math.floor(t * N_PRESS);    for (let y = TIP[1]; y <= SURF; y++) {      const b = ((y - tick) % 3 + 3) % 3;      g.pixel(TIP[0], y, b === 0 ? "g3" : "g2");      g.pixel(TIP[0] + 1, y, b === 1 ? "g2" : "g1");    }    const k = tick % 2;                                                    // splash at the surface    g.pixel(TIP[0] - 1 - k, SURF, "g3"); g.pixel(TIP[0] + 2 + k, SURF, "n5"); g.pixel(TIP[0] - 1 + 3 * k, SURF + 1, "g2");  });}function stateAt(state, t) {  t = ((t % 1) + 1) % 1;  return { mode: state, t };}const CiderPress = sprite(pal, (g, t0, state) => {  const S = stateAt(state, t0), t = S.t;  const press = S.mode === "press";  const py = press ? BTY : BTY - 2;                                   // plate centre row  const th = press ? 180 * t : 0;  const shift = press ? Math.floor(t * 6) : 0;  contactShadow(g, 18, GROUND, 18, 1);  contactShadow(g, BKX, 62, 6, 1);  g.outlined("ink", () => {    bed(g, S);    frame(g);    screw(g, 16, py - 5, shift);    basket(g, S, t, py);    plateAndBlock(g, py);  });  screw(g, 9, 9, shift);  spout(g, S, t);  bar(g, th);  beamApple(g);  if (press) basketDrips(g, t);  bucket(g, S, t);  if (S.mode === "idle") drip(g, t, N_IDLE);  if (press) stream(g, t);  if (S.mode === "full") { drip(g, t, N_FULL); froth(g, t); wasp(g, t); }});submit(CiderPress, {  size: [W, H], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: CX, y: GROUND },  args: ["idle"],  variants: [    { name: "idle", args: ["idle"] },    { name: "press", args: ["press"], animate: { fps: 10, duration: N_PRESS / 10, loop: true } },    { name: "full", args: ["full"], animate: { fps: 10, duration: N_FULL / 10, loop: true } },  ],});

掲載されたコードでは、ビルドスクリプトが自動付与する共通部分を省略しています。具体的には 1 行のランタイムヘッダー、共有パレット、および汎用ヘルパー関数(contactShadow、litIndex、flame、hash、chk、TAU)です。表示されている行番号は実際のファイルと一致しています。これらのプログラムは AIエージェントが生成し、描画されたコマの仕上がりを確認しながら修正を加えたものです。

エージェントに、こうしたプログラムを書かせる。

Tessarune は描画 API のリファレンス、25 冊の制作マニュアル、結果を確かめるための描画エンジンをエージェントに渡します。