Tessarune
Arcane Curiosities Vol.1 · mimic.js

ミミック

48×48 · JavaScript 442 行

48×48 のミミックで、宝箱と同じ作りを少しゆがめています。腹がふくらんで息をし、板は曲がり、蓋の合わせ目に不ぞろいな歯が並び、前に舌が垂れ、すき間で片目が光ります。宝箱と同じく、4 方向は一つのミミックを床の上で回したもので、世界の側の左上から光が当たります。各方向に idle(12 コマ、蓋が 1〜2px 開いて息をし、腹がふくらみ、舌が揺れ、歯を光が走る)と bite(10 コマのループ、蓋が大きく開いて飛びかかり、つぶれながら噛み閉じる)があります。

ミミック — 描画結果6×
このプログラムで描画(変種「bite-down」) · 48×48 · 10 コマ
Arcane Curiosities Vol.1動くスプライト素材: Arcane Curiosities Vol.1(itch.io)動く小物 12 点・52 アニメ

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

mimic.js · 1–9
// Mimic — 48x48, top-down 3/4, key light top-left. The treasure chest's design language (same plank box, barrel// lid on a back hinge, iron bands, brass caps and lock), slightly warped: the belly bows out and breathes, the// planks run crooked, uneven teeth line the lid seam, a tongue lolls over the front and one eye glints in the gap.// Built like chest.js: every pixel casts the oblique view ray (screen y = BASE - z - K*depth) into a few convex// solids, so the four facings are one mimic turned on the floor, lit top-left in world space (right is not a// mirrored left). Teeth are flat tooth sheets on the seam planes; the tongue is a chain of spheres.// Variants (f = down / up / left / right)://   idle-f  loop, 12f — lid breathes 1-2px open, belly swells, tongue sways, a glint runs along the teeth//   bite-f  loop, 10f — lid snaps wide (teeth rows, palate, tongue lashing up), lunges, chomps shut with a squash

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

mimic.js · 434–442
submit(Mimic, {  size: [48, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    ...FACES.map((f) => ({ name: `idle-${f}`, args: ["idle", f] })),    ...FACES.map((f) => ({ name: `bite-${f}`, args: ["bite", f], animate: { fps: 10, duration: N_BITE / 10, loop: true } })),  ],});
プログラム全体を表示 (442 行)
mimic.js · 442 行
// Mimic — 48x48, top-down 3/4, key light top-left. The treasure chest's design language (same plank box, barrel// lid on a back hinge, iron bands, brass caps and lock), slightly warped: the belly bows out and breathes, the// planks run crooked, uneven teeth line the lid seam, a tongue lolls over the front and one eye glints in the gap.// Built like chest.js: every pixel casts the oblique view ray (screen y = BASE - z - K*depth) into a few convex// solids, so the four facings are one mimic turned on the floor, lit top-left in world space (right is not a// mirrored left). Teeth are flat tooth sheets on the seam planes; the tongue is a chain of spheres.// Variants (f = down / up / left / right)://   idle-f  loop, 12f — lid breathes 1-2px open, belly swells, tongue sways, a glint runs along the teeth//   bite-f  loop, 10f — lid snaps wide (teeth rows, palate, tongue lashing up), lunges, chomps shut with a squashconst CX = 24.5, GROUND = 45, K = 0.4;const L = 31, D = 19, H = 12, WALL = 3, R = 5;const HL = L / 2, HD = D / 2, T = 1.6, LIDZ = H + WALL, BX = 8;const N_IDLE = 12, N_BITE = 10, WIDE = 66;const LV = norm3([-0.6, -0.25, 0.76]);const WOOD = ["w0", "w1", "w2", "w3"], IRON = ["n0", "n1", "n2", "n3"], GOLD = ["g0", "g1", "g2", "g3"];function norm3(v) { const m = Math.hypot(v[0], v[1], v[2]); return [v[0] / m, v[1] / m, v[2] / m]; }const FACINGS = {  down: { toL: (v) => [v[0], v[1], v[2]], toW: (v) => [v[0], v[1], v[2]], ext: HD, shadow: 17 },  up: { toL: (v) => [-v[0], -v[1], v[2]], toW: (v) => [-v[0], -v[1], v[2]], ext: HD, shadow: 17 },  right: { toL: (v) => [v[1], -v[0], v[2]], toW: (v) => [-v[1], v[0], v[2]], ext: HL, shadow: 12 },  left: { toL: (v) => [-v[1], v[0], v[2]], toW: (v) => [v[1], -v[0], v[2]], ext: HL, shadow: 12 },};// ---- solidsconst quad = (c, r) => ({ c, inv2: r.map((v) => (isFinite(v) ? 1 / (v * v) : 0)) });const BODY = [-HL, HL, -HD, HD, 0, H];const CAV = [-HL + T, HL - T, -HD + T, HD - T, T, 99];const LOCK = [-3.5, 3.5, -HD - 0.8, -HD, H - 8, H - 3];const HANDLES = [[HL, HL + 1, -1.5, 1.5, H - 4, H - 2], [-HL - 1, -HL, -1.5, 1.5, H - 4, H - 2]];const KNUCKLES = [[2.5, 5.5, HD, HD + 1, H - 1, H + 1], [-5.5, -2.5, HD, HD + 1, H - 1, H + 1]];const LIDBOX = [-HL, HL, -HD, HD, H, LIDZ];const BARREL = [-HL, HL, -Infinity, Infinity, LIDZ, Infinity], BARREL_Q = quad([0, 0, LIDZ], [Infinity, HD, R]);const ANY = [-99, 99, -99, 99, -99, 99];const EYE_X = -5.25, EYE_Q = quad([EYE_X, -HD + 2.6, H + 0.8], [1.9, 1.4, 1.6]);const BOUND_C = [-HL - 1.1, HL + 1.1, -HD - 5, HD + 1.1, 0, H + 11];const BOUND_L = [-HL - 0.4, HL + 0.4, -HD - 0.4, HD + 0.1, H - 3.8, LIDZ + R + 0.1];// teeth: flat sheets on the seam planes. [centre along the edge, half-width, length]; uneven on purposeconst UPPER_FRONT = [[-13.6, 1.1, 2.2], [-11, 1.4, 4.4], [-8, 1.5, 2.7], [-2, 1.5, 2.9], [1.5, 1.5, 2.5], [5, 1.5, 3],  [8, 1.5, 2.6], [11, 1.4, 4.2], [13.6, 1.1, 2.3]];   // fangs at +-11, a gap at x=-5 where the eye looks outconst LOWER_FRONT = [-11.5, -8.5, -2, 1, 7.5, 10.5].map((c, i) => [c, 1.5, 2.2 + 0.7 * hash(i, 11)]);const UPPER_SIDE = [-7.5, -4, -0.5].map((c, i) => [c, 1.7, 2.6 + 0.7 * hash(i, 13)]);const LOWER_SIDE = [-5.75, -2.25, 1.25].map((c, i) => [c, 1.5, 2.2 + 0.5 * hash(i, 17)]);// [axis of the sheet plane, plane value, outward sign, edge axis, teeth, grows down (upper) or up (lower)]const UPPER_SHEETS = [[1, -HD - 0.25, -1, 0, UPPER_FRONT], [0, HL + 0.25, 1, 1, UPPER_SIDE], [0, -HL - 0.25, -1, 1, UPPER_SIDE]];const LOWER_SHEETS = [[1, -HD + 0.9, -1, 0, LOWER_FRONT], [0, HL - 0.9, 1, 1, LOWER_SIDE], [0, -HL + 0.9, -1, 1, LOWER_SIDE]];let TIN = 0, TOUT = 0, CIN = -1, COUT = -1;function convex(o, d, b, q) {  let tin = -1e9, tout = 1e9, cin = -1, cout = -1;  for (let i = 0; i < 3; i++) {    const lo = b[2 * i], hi = b[2 * i + 1], di = d[i], oi = o[i];    if (lo === -Infinity && hi === Infinity) continue;    if (di < 1e-9 && di > -1e-9) { if (oi < lo || oi > hi) return false; continue; }    let t1 = (lo - oi) / di, t2 = (hi - oi) / di, c1 = 2 * i, c2 = 2 * i + 1;    if (t1 > t2) { const k = t1; t1 = t2; t2 = k; c1 = 2 * i + 1; c2 = 2 * i; }    if (t1 > tin) { tin = t1; cin = c1; }    if (t2 < tout) { tout = t2; cout = c2; }    if (tin > tout) return false;  }  if (q) {    const r = q.inv2, c = q.c;    const ox = o[0] - c[0], oy = o[1] - c[1], oz = o[2] - c[2];    const A = d[0] * d[0] * r[0] + d[1] * d[1] * r[1] + d[2] * d[2] * r[2];    const B = 2 * (ox * d[0] * r[0] + oy * d[1] * r[1] + oz * d[2] * r[2]);    const C = ox * ox * r[0] + oy * oy * r[1] + oz * oz * r[2] - 1;    const disc = B * B - 4 * A * C;    if (disc < 0) return false;    const sq = Math.sqrt(disc), r1 = (-B - sq) / (2 * A), r2 = (-B + sq) / (2 * A);    if (r1 > tin) { tin = r1; cin = 6; }    if (r2 < tout) { tout = r2; cout = 6; }    if (tin > tout) return false;  }  TIN = tin; TOUT = tout; CIN = cin; COUT = cout;  return true;}function normalOf(code, p, q) {  if (code < 6) { const n = [0, 0, 0]; n[code >> 1] = code & 1 ? 1 : -1; return n; }  return norm3([(p[0] - q.c[0]) * q.inv2[0], (p[1] - q.c[1]) * q.inv2[1], (p[2] - q.c[2]) * q.inv2[2]]);}// ray vs one tooth sheet: returns [t, tooth index] or null. `down` teeth hang from z=H, others rise from it.// Seen edge-on (the ray runs along the sheet) a tooth is a 1px sliver: march the ray through the sheet's thickness.function toothAt(teeth, s, h) {  if (h < 0) return -1;  for (let i = 0; i < teeth.length; i++) {    const [c, w, len] = teeth[i], u = Math.abs(s - c);    if (u < w && h <= len * (1 - u / w)) return i;  }  return -1;}function sheetHit(o, d, sh, down) {  const [ax, at, , ea, teeth] = sh;  if (d[ax] < 1e-6 && d[ax] > -1e-6) {    if (Math.abs(o[ax] - at) > 0.6) return null;    const zA = down ? H - 4.6 : H, zB = down ? H : H + 3.2;       // tooth band heights    const tA = (zB - o[2]) / d[2], tB = (zA - o[2]) / d[2];        // d[2] < 0: the ray descends    for (let t = Math.min(tA, tB); t <= Math.max(tA, tB); t += 0.25) {      const i = toothAt(teeth, o[ea] + t * d[ea], down ? H - (o[2] + t * d[2]) : o[2] + t * d[2] - H);      if (i >= 0) return [t, i];    }    return null;  }  const t = (at - o[ax]) / d[ax];  const i = toothAt(teeth, o[ea] + t * d[ea], down ? H - (o[2] + t * d[2]) : o[2] + t * d[2] - H);  return i >= 0 ? [t, i] : null;}// the tongue: a chain of spheres along 4 control points, between hanging (u=0) and lashing up (u=1)function tongueChain(u, sw) {  const lerp3 = (a, b) => a.map((v, i) => v + (b[i] - v) * u);  const P = [    lerp3([2, -1, H - 2.5], [1, 1, H - 2]),    lerp3([4, -HD + 0.4, H + 0.6], [2, -3, H + 4]),    lerp3([4.5 + 0.6 * sw, -HD - 1.4, H - 3.5], [3 + 1.5 * sw, -HD, H + 9]),    lerp3([5 + 1.8 * sw, -HD - 1.8, H - 8], [1 + 3 * sw, -HD - 3, H + 11]),  ];  const out = [];  for (let k = 0; k < 9; k++) {    const f = (k / 8) * 3, i = Math.min(2, Math.floor(f)), a = f - i;    const c = P[i].map((v, j) => v + (P[i + 1][j] - v) * a);    const rad = 2.8 - 1.0 * (k / 8);    out.push(quad(c, [rad, rad * 0.8, rad * 0.85]));  }  return out;}function castMimic(face, st) {  const F = FACINGS[face], BASE = GROUND - K * F.ext;  const a = (st.th * Math.PI) / 180, ca = Math.cos(a), sa = Math.sin(a);  const lidNormal = (n) => [n[0], n[1] * ca + n[2] * sa, -n[1] * sa + n[2] * ca];  const d = F.toL([0, 1 / st.sx, -K / st.sz]);  const dl = [d[0], d[1] * ca - d[2] * sa, d[1] * sa + d[2] * ca];  const ex = F.toL([1, 0, 0]), ez = F.toL([0, 0, 1]);  const tongue = tongueChain(st.tongue, st.sway);  const proj = (q) => {    const w = F.toW([q[0], q[1] - st.lunge, q[2]]);    const sy = BASE - st.hop - w[2] * st.sz - K * w[1] * st.sx;    const zf = GROUND - sy - st.hop, bow = 1 + st.bulge * Math.sin(Math.PI * Math.max(0, Math.min(1, zf / (H + 4))));    return [CX + w[0] * st.sx * bow - 0.5, sy - 0.5, w[1] * st.sx];  };  // screen rectangle of a (finite) box in the chest or lid frame: pixels outside it skip that solid entirely  const rect = (b, lid) => {    const r = [99, -99, 99, -99];    for (const x of [b[0], b[1]]) for (const y of [b[2], b[3]]) for (const z of [b[4], b[5]]) {      const [sx, sy] = proj(lid ? lidToChest([x, y, z], st.th) : [x, y, z]);      r[0] = Math.min(r[0], sx); r[1] = Math.max(r[1], sx); r[2] = Math.min(r[2], sy); r[3] = Math.max(r[3], sy);    }    return [Math.floor(r[0]) - 2, Math.ceil(r[1]) + 2, Math.floor(r[2]) - 2, Math.ceil(r[3]) + 2];  };  const qbox = (q) => q.c.flatMap((c, i) => [c - 1 / Math.sqrt(q.inv2[i]), c + 1 / Math.sqrt(q.inv2[i])]);  const tb = [99, -99, 99, -99, 99, -99];  for (const q of tongue) { const b = qbox(q); for (let i = 0; i < 6; i += 2) { tb[i] = Math.min(tb[i], b[i]); tb[i + 1] = Math.max(tb[i + 1], b[i + 1]); } }  const R_BODY = rect(BODY, false), R_LOCK = rect(LOCK, false), R_EYE = rect(qbox(EYE_Q), false), R_TONGUE = rect(tb, false);  const R_HANDLES = HANDLES.map((h) => rect(h, false)), R_KNUCKLES = KNUCKLES.map((k) => rect(k, false));  const R_LOWER = rect([-HL, HL, -HD, HD, H, H + 3.4], false), R_UPPER = rect([-HL - 0.6, HL + 0.6, -HD - 0.6, HD, H - 4.6, H], true);  const R_LID = rect([-HL, HL, -HD, HD, H, LIDZ + R], true);  const o = [0, 0, 0], ol = [0, 0, 0];  const buf = new Array(48 * 48).fill(null);  for (let py = 0; py < 48; py++) {    // the warp: the belly bows out, most at mid-height of the near face    const zf = GROUND - 0.5 - py - st.hop, bow = 1 + st.bulge * Math.sin(Math.PI * Math.max(0, Math.min(1, zf / (H + 4))));    for (let px = 0; px < 48; px++) {      const Xw = (px + 0.5 - CX) / (st.sx * bow), Zw = (BASE - py - 0.5 - st.hop) / st.sz;      for (let i = 0; i < 3; i++) o[i] = ex[i] * Xw + ez[i] * Zw;      o[1] += st.lunge;      ol[0] = o[0]; ol[1] = HD + (o[1] - HD) * ca - (o[2] - H) * sa; ol[2] = H + (o[1] - HD) * sa + (o[2] - H) * ca;      const inR = (r) => px >= r[0] && px <= r[1] && py >= r[2] && py <= r[3];      const inC = convex(o, d, BOUND_C, null), inL = convex(ol, dl, BOUND_L, null);      if (!inC && !inL) continue;      let bt = 1e9, bid = null, blid = false, bcode = -1, bq = null, bflip = false, bextra = 0;      const hit = (id, lid, box, q) => {        if (convex(lid ? ol : o, lid ? dl : d, box, q) && TIN < bt) { bt = TIN; bid = id; blid = lid; bcode = CIN; bq = q; bflip = false; }      };      if (inC) {        if (inR(R_BODY) && convex(o, d, BODY, null)) {          const tb = TIN, cb = CIN, qx = o[0] + tb * d[0], qy = o[1] + tb * d[1];          if (cb === 5 && Math.abs(qx) < HL - T && Math.abs(qy) < HD - T) {            if (convex(o, d, CAV, null) && TOUT < bt) { bt = TOUT; bid = "inner"; blid = false; bcode = COUT; bq = null; bflip = true; }          } else if (tb < bt) { bt = tb; bid = "body"; blid = false; bcode = cb; bq = null; bflip = false; }        }        if (inR(R_LOCK)) hit("lock", false, LOCK, null);        for (let i = 0; i < 2; i++) {          if (inR(R_HANDLES[i])) hit("handle", false, HANDLES[i], null);          if (inR(R_KNUCKLES[i])) hit("knuckle", false, KNUCKLES[i], null);        }        if (inR(R_EYE)) hit("eye", false, ANY, EYE_Q);        if (inR(R_TONGUE)) for (const q of tongue) hit("tongue", false, ANY, q);        if (inR(R_LOWER)) for (const sh of LOWER_SHEETS) {          const r = sheetHit(o, d, sh, false);          if (r && r[0] < bt) { bt = r[0]; bid = "lower"; blid = false; bcode = 2 * sh[0] + (sh[2] > 0 ? 1 : 0); bq = null; bflip = false; bextra = r[1]; }        }      }      if (inL) {        if (inR(R_LID)) { hit("lidbox", true, LIDBOX, null); hit("barrel", true, BARREL, BARREL_Q); }        if (inR(R_UPPER)) for (const sh of UPPER_SHEETS) {          const r = sheetHit(ol, dl, sh, true);          if (r && r[0] < bt) { bt = r[0]; bid = "upper"; blid = true; bcode = 2 * sh[0] + (sh[2] > 0 ? 1 : 0); bq = null; bflip = false; bextra = r[1]; }        }      }      if (!bid) continue;      const oo = blid ? ol : o, dd = blid ? dl : d;      const p = [oo[0] + bt * dd[0], oo[1] + bt * dd[1], oo[2] + bt * dd[2]];      let n = normalOf(bcode, p, bq);      if (bflip) n = [-n[0], -n[1], -n[2]];      const nw = F.toW(blid ? lidNormal(n) : n);      const c = classify(bid, p, n, bextra);      c.lid = blid; c.t = bt; c.d = nw[0] * LV[0] + nw[1] * LV[1] + nw[2] * LV[2];      buf[py * 48 + px] = c;    }  }  return { buf, proj };}// a point in the lid's rest frame -> chest-local, with the lid open by th degreesfunction lidToChest(q, th) {  const a = (th * Math.PI) / 180, dy = q[1] - HD, dz = q[2] - H;  return [q[0], HD + dy * Math.cos(a) + dz * Math.sin(a), H - dy * Math.sin(a) + dz * Math.cos(a)];}function classify(id, p, n, extra) {  const x = p[0], y = p[1], z = p[2], ax = Math.abs(x), ay = Math.abs(y);  const band = Math.abs(ax - BX) <= 1.5, hinge = ax >= 2.5 && ax <= 5.5;  const rivet = Math.abs(ax - BX) < 0.5 && Math.abs((((z % 4) + 4) % 4) - 1.5) < 0.5;  const face = n[2] > 0.5 ? "top" : n[2] < -0.5 ? "under" : n[1] < -0.5 ? "front" : n[1] > 0.5 ? "back" : n[0] > 0 ? "endR" : "endL";  const u = face === "front" ? x : face === "back" ? -x : y;  const CAP = { part: "caps", mat: "brass" }, BAND = { part: "bands", mat: "iron", rivet };  if (id === "upper" || id === "lower") return { part: "teeth", mat: "tooth", s: id, k: extra };  if (id === "tongue") return { part: "tongue", mat: "tongue" };  if (id === "eye") return { part: "eye", mat: "eye", x };  if (id === "body") {    if (face === "top") return { part: "maw", mat: "gum" };    const end = face === "endL" || face === "endR";    if (z < 3.5 && (end ? ay > HD - 3.5 : ax > HL - 3.5)) return CAP;    if (!end && band) return BAND;    if (face === "back" && hinge && z > H - 4) return { part: "hinges", mat: "iron", rivet: Math.abs(ax - 4) < 0.5 && Math.abs(z - 9.5) < 0.5 };    if (end) {      const ry = y / 2.5, rz = (z - (H - 6)) / 2, rr = ry * ry + rz * rz;      if (rr > 0.3 && rr < 1.1) return { part: "handles", mat: "iron", ring: true };    }    // crooked planks: the seams slope across the face    return { part: "body", mat: "wood", s: face, k: Math.floor((z + 0.09 * u + 0.6) / 4), u, z };  }  if (id === "inner") return { part: "maw", mat: "maw", s: face };  if (id === "lock") return { part: "lock", mat: "brass", key: face === "front" && ax < 0.5 && z > 5.5 && z < 7.5 };  if (id === "handle") return { part: "handles", mat: "iron", ring: true };  if (id === "knuckle") return { part: "hinges", mat: "iron", knuckle: true, z };  if (id === "lidbox") {    if (face === "under") return { part: "maw", mat: "palate", s: "under", k: Math.floor((y + HD) / 4), x, y };    const end = face === "endL" || face === "endR";    if (end ? ay > HD - 3.5 : ax > HL - 3.5) return CAP;    if (z < H + 1) return { part: "bands", mat: "iron", rim: true };    if (!end && band) return BAND;    if (face === "back" && hinge) return { part: "hinges", mat: "iron" };    return { part: "lid", mat: "wood", s: "wall" + face, k: 0, u };  }  if (Math.abs(n[0]) > 0.9) {    const e = Math.hypot(y / HD, (z - LIDZ) / R);    if (e > 0.8) return { part: "bands", mat: "iron", rim: true };    return { part: "lid", mat: "wood", s: face, k: 0, u: y };  }  const phi = Math.atan2((z - LIDZ) / R, y / HD);  if (band) return BAND;  if (hinge && phi < 0.45) return { part: "hinges", mat: "iron" };  return { part: "lid", mat: "wood", s: "barrel", k: Math.floor((phi + 0.012 * x) / (Math.PI / 3)), u: x };}function level(d) { return d > 0.78 ? 3 : d > 0.45 ? 2 : d > 0.1 ? 1 : 0; }function tone(buf, fx) {  const keys = new Array(48 * 48).fill(null);  for (let i = 0; i < 48 * 48; i++) {    const b = buf[i]; if (!b) continue;    const px = i % 48, py = (i - px) / 48;    const up = py > 0 ? buf[i - 48] : null, dn = py < 47 ? buf[i + 48] : null;    const lf = px > 0 ? buf[i - 1] : null, rt = px < 47 ? buf[i + 1] : null;    const samePart = (q) => q && q.part === b.part;    const glint = fx.glint ? fx.glint(px, py) : 0;    let key;    if (b.mat === "wood") {      let lv = level(b.d);      const seam = (q) => q && q.s === b.s && q.k !== b.k;      if (seam(rt) || seam(dn)) lv = 0;      else if (seam(up) || seam(lf)) lv = Math.min(3, lv + 1);      else if (b.z !== undefined && Math.floor(b.z) % 4 === 2 && hash(b.k * 3 + (b.s === "front" ? 0 : 11), Math.floor((b.u + 40) / 4)) > 0.62 && Math.floor(b.u + 40) % 4 !== 0) lv = Math.max(0, lv - 1);      if (!b.lid && up && up.lid && b.s !== "top") lv = 0;      if (!up && lv > 0) lv = Math.min(3, lv + 1);      key = WOOD[Math.max(0, Math.min(3, lv))];    } else if (b.mat === "iron") {      let lv = level(b.d);      if (!samePart(lf) && !b.rim) lv = Math.min(3, lv + 1);      else if (!samePart(rt) && !b.rim) lv = Math.max(0, lv - 1);      if (b.rim && !samePart(up)) lv = Math.min(3, lv + 1);      if (b.ring) lv = Math.max(lv, 2) + (!samePart(up) || !samePart(lf) ? 1 : 0) - (!samePart(dn) ? 1 : 0);      if (b.knuckle) lv = b.z > H ? 3 : 1;      lv = Math.max(0, Math.min(3, lv));      key = b.rivet ? "n4" : lv === 3 && (b.d > 0.85 || (b.knuckle && !samePart(lf))) ? "n4" : IRON[lv];    } else if (b.mat === "brass") {      const same = (q) => q && q.mat === "brass";      let lv = b.d > 0.5 ? 2 : 1;      if (!same(up)) lv = b.d > 0.1 ? 3 : 2;      else if (!same(lf)) lv = 2;      else if (!same(dn) || !same(rt)) lv = 0;      key = b.key ? "ink" : GOLD[lv];    } else if (b.mat === "tooth") {      // bone: lit left edge, shaded right edge, dark root in the gum line; the glint turns a tooth white-hot      const sameT = (q) => q && q.mat === "tooth" && q.s === b.s && q.k === b.k;      let k = "n4";      if (!sameT(lf)) k = b.s === "upper" ? "n5" : "n4";      else if (!sameT(rt)) k = "n3";      if (b.s === "upper" ? !sameT(dn) && sameT(up) : !sameT(up) && sameT(dn)) k = k === "n5" ? "n4" : "n3";      if (glint > 0) k = "n5";      key = k;    } else if (b.mat === "tongue") {      const lv = b.d > 0.72 ? 3 : b.d > 0.4 ? 2 : b.d > 0.05 ? 1 : 0;      key = ["r0", "r1", "r2", "v3"][lv];      if (lv === 3 && samePart(up)) key = "r2";   // only the top rim gets the wet highlight    } else if (b.mat === "eye") {      const cxp = Math.abs(b.x - EYE_X);      // a slit pupil once the eye shows more than a sliver; in the idle gap it is just a hot amber glint      const tall = samePart(up) && samePart(dn);      key = cxp < 0.5 && tall ? "ink" : fx.eyeGlint && !samePart(up) && !samePart(lf) ? "n5" : cxp < 1.2 ? "g3" : "g2";    } else if (b.mat === "maw") {      key = b.s === "top" ? "ink" : b.s === "endL" || b.s === "endR" ? "r0" : samePart(up) ? "v0" : "r0";    } else if (b.mat === "gum") {      key = "r0";    } else if (b.mat === "palate") {      // roof of the mouth: warm near the teeth, dark down the throat, a groove down the middle, ridges across      const seam = (q) => q && q.s === b.s && q.k !== b.k;      const deep = (b.y + HD) / D;             // 0 at the front edge (teeth), 1 at the hinge (throat)      const zone = deep < 0.3 ? 2 : deep < 0.62 ? 1 : 0;      const ramp = ["ink", "v0", "r0", "r1"];      key = ramp[zone + 1];      if (seam(dn) || seam(rt) || Math.abs(b.x + 0.5) < 0.6) key = ramp[zone];    }    keys[i] = key;  }  return keys;}const PARTS = ["body", "lid", "maw", "tongue", "eye", "teeth", "bands", "hinges", "handles", "caps", "lock"];function drawMimic(g, face, st, fx) {  const fr = castMimic(face, st);  const buf = fr.buf, keys = tone(buf, fx);  let x0 = 47, x1 = 0, y0 = 47, y1 = 0;  const seen = new Set();   // only declare the parts this view actually shows (an occluded part is not a silent one)  for (let i = 0; i < 48 * 48; i++) if (buf[i]) { const x = i % 48, y = (i - x) / 48; x0 = Math.min(x0, x); x1 = Math.max(x1, x); y0 = Math.min(y0, y); y1 = Math.max(y1, y); seen.add(buf[i].part); }  g.outlined("ink", () => {    for (const part of PARTS) if (seen.has(part)) g.part(part, () => g.field({ x: [x0, x1], y: [y0, y1] }, (x, y) => {      const b = buf[y * 48 + x];      return b && b.part === part ? keys[y * 48 + x] : null;    }));  });  return fr;}function visible(buf, x, y, depth, slack) {  if (x < 0 || y < 0 || x > 47 || y > 47) return false;  const b = buf[y * 48 + x];  return !b || depth < b.t + slack;}function star(g, buf, x, y, depth, size) {  const put = (px, py, k) => { if (visible(buf, px, py, depth, 1)) g.pixel(px, py, k); };  put(x, y, "n5");  for (const [dx, dy] of [[1, 0], [-1, 0], [0, 1], [0, -1]]) put(x + dx, y + dy, size > 1 ? "n5" : "n4");  if (size > 1) for (const [dx, dy] of [[2, 0], [-2, 0], [0, 2], [0, -2]]) put(x + dx, y + dy, "n4");}// ---- statesconst BITE = [  // th, sx, sz, hop, lunge, bulge  [3.5, 1, 1, 0, 0, 0.04],  [16, 0.98, 1.05, 0, 0, 0.05],       // the lid starts to rise, the body stretches  [50, 0.97, 1.07, 1, 0.6, 0.05],  [WIDE, 1, 1.04, 2, 1.5, 0.06],      // wide open, lunging: teeth rows, palate, tongue lashing up  [WIDE + 2, 1, 1.02, 2, 1.5, 0.06],  [30, 1, 1, 1, 1, 0.05],             // snapping down  [0, 1.07, 0.86, 0, 0.5, 0.09],      // CHOMP: squash  [0, 0.97, 1.05, 0, 0, 0.03],        // rebound  [2, 1.01, 0.98, 0, 0, 0.04],  [3, 1, 1, 0, 0, 0.04],];const Mimic = sprite(pal, (g, t, state, face) => {  contactShadow(g, 24, GROUND, FACINGS[face].shadow, 2);  if (state === "idle") {    const breath = 0.5 - 0.5 * Math.cos(TAU * t);    const st = { th: 3.5 + 3 * breath, sx: 1, sz: 1, hop: 0, lunge: 0, bulge: 0.035 + 0.03 * breath, tongue: 0, sway: Math.sin(TAU * t) };    // a glint runs along the teeth left to right, then rests    const gp = 10 + (t / 0.7) * 30;    const fr = drawMimic(g, face, st, {      glint: t < 0.7 ? (x, y) => (Math.abs(x + (y - 30) * 0.6 - gp) < 1.8 ? 1 : 0) : null,      eyeGlint: t > 0.55 && t < 0.9,    });    g.detached("glint", () => {      if (t >= 0.7) return;      // a star pops at the tip of each fang as the glint passes it      for (const [c, , len] of UPPER_FRONT) {        if (len < 3) continue;        const [sx, sy, dep] = fr.proj(lidToChest([c, -HD - 0.3, H - len + 0.8], st.th));        const x = Math.round(sx), y = Math.round(sy);        if (Math.abs(x + (y - 30) * 0.6 - gp) < 1.8) star(g, fr.buf, x, y, dep - 1, 1);      }    });    return;  }  const f = Math.min(N_BITE - 1, Math.round(t * N_BITE));  const [th, sx, sz, hop, lunge, bulge] = BITE[f];  const st = { th, sx, sz, hop, lunge, bulge, tongue: Math.min(1, th / WIDE), sway: Math.sin(TAU * t + 1) };  const fr = drawMimic(g, face, st, { eyeGlint: f >= 2 && f <= 4 });  if (f === 6) {    // chomp: clack marks burst from the front corners of the seam    g.detached("clack", () => {      for (const s of [-1, 1]) {        const [sx0, sy0, dep] = fr.proj([s * (HL + 1), -HD - 1, H + 0.5]);        const x = Math.round(sx0), y = Math.round(sy0);        for (let k = 1; k <= 2; k++) {          for (const [dx, dy] of [[s * (k + 1), -k], [s * (k + 2), 0], [s * (k + 1), k]]) {            if (visible(fr.buf, x + dx, y + dy, dep - 3, 0)) g.pixel(x + dx, y + dy, k === 1 ? "n5" : "n4");          }        }      }    });  }});const FACES = ["down", "up", "left", "right"];submit(Mimic, {  size: [48, 48], origin: "top-left", kind: "icon", scale: 1,  animate: { fps: 10, duration: N_IDLE / 10, loop: true },  anchor: { x: 24, y: GROUND },  variants: [    ...FACES.map((f) => ({ name: `idle-${f}`, args: ["idle", f] })),    ...FACES.map((f) => ({ name: `bite-${f}`, args: ["bite", f], animate: { fps: 10, duration: N_BITE / 10, loop: true } })),  ],});

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

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

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