Graveyard & Chapel · crypt_door.js
霊廟の扉
64×64 の床置きの小物で、正面を向いています。翼のある髑髏を彫った破風、段のある台座に立つ 2 本の柱、紫の紋章を刻んだ要石を備えた小さな霊廟の石の正面に、鉄帯で補強した板の重い両開き扉と輪の取っ手が付いています。closed(12 コマ)は敷居に沿って白い霧が這い、紋章が脈打ちます。open(10 コマの一回再生)は封印が光り、扉が内側へ開いて行き過ぎてから止まり、まぐさから砂が落ち、祭壇の魂火が浮かび上がります。open-idle(12 コマ)は魂火が息づき、霧が段の上へあふれます。
6×
動くスプライト素材: Graveyard & Chapel(itch.io)Vol.4・小物 12 点・33 アニメ
冒頭のコメント。これが指示書です。サイズ、視点、光の向き、すべての変種が書かれています。
crypt_door.js · 1–9
// Crypt door — 64x64 floor prop, top-down 3/4, key light top-left. Camera-facing.// The stone facade of a small mausoleum: a pediment with a carved winged skull under an orb finial, a dentilled// cornice, two columns on a stepped plinth, an ashlar wall with a moulded door surround, and a keystone carrying a// violet sigil. Heavy double doors of iron-banded planks with ring pulls.// Variants:// closed 12f loop pale fog creeps out along the doorsill, the sigil on the keystone pulses// open 10f once the seal flares, the doors jolt and swing inward (overshoot, settle), grit falls from the// lintel; the dark tomb and a soulfire on an altar fade in; ends on open-idle frame 0// open-idle 12f loop doors open, the soulfire breathes on its altar, fog spills out over the steps最後の数行。submit でキャンバスの大きさと、描画エンジンが書き出す変種の一覧を指定します。
crypt_door.js · 376–386
submit(Crypt, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: N_IDLE / 10, loop: true }, anchor: { x: 32, y: GROUND }, args: ["closed"], variants: [ { name: "closed", args: ["closed"] }, { name: "open", args: ["open"], animate: { fps: 10, duration: N_OPEN / 10, loop: false } }, { name: "open-idle", args: ["open-idle"] }, ],});プログラム全体を表示 (386 行)
crypt_door.js · 386 行
// Crypt door — 64x64 floor prop, top-down 3/4, key light top-left. Camera-facing.// The stone facade of a small mausoleum: a pediment with a carved winged skull under an orb finial, a dentilled// cornice, two columns on a stepped plinth, an ashlar wall with a moulded door surround, and a keystone carrying a// violet sigil. Heavy double doors of iron-banded planks with ring pulls.// Variants:// closed 12f loop pale fog creeps out along the doorsill, the sigil on the keystone pulses// open 10f once the seal flares, the doors jolt and swing inward (overshoot, settle), grit falls from the// lintel; the dark tomb and a soulfire on an altar fade in; ends on open-idle frame 0// open-idle 12f loop doors open, the soulfire breathes on its altar, fog spills out over the stepsconst W = 64, H = 64, CX = 31.5, GROUND = 61;const DX0 = 22, DX1 = 41, DY0 = 31, DY1 = 53; // doorway (inclusive)const LEAF = 10, RISE = 4; // leaf width, how far a fully turned free edge rises (depth)const SLOPE = 2.1, APEX = 4.5; // pediment rakeconst N_IDLE = 12, N_OPEN = 10, REST = 80;const OPEN_TH = [0, 3, 9, 21, 37, 53, 67, 79, 85, REST];const WOOD = ["w0", "w1", "w2", "w3", "w4"], STONE = ["n0", "n1", "n2", "n3", "n4"];const clampI = (i, n) => Math.max(0, Math.min(n - 1, i));function dim(key, k) { // step a key down its own ramp if (!k) return key; const r = key[0] === "w" ? WOOD : key[0] === "n" ? STONE : null; if (!r) return key; const i = r.indexOf(key); return i < 0 ? key : (i - k < 0 ? "ink" : r[i - k]);}const BAYER = [[0, 8, 2, 10], [12, 4, 14, 6], [3, 11, 1, 9], [15, 7, 13, 5]];const thr = (x, y) => (BAYER[y & 3][x & 3] + 0.5) / 16;// ---- facade regions ----------------------------------------------------------------------------------------const pedEdge = (x) => APEX + Math.abs(x + 0.5 - 32) / SLOPE; // top edge of the raking cornicefunction region(x, y) { if (y <= 5 && x >= 29 && x <= 34) return "finial"; if (y >= 5 && y <= 16 && y >= pedEdge(x) - 0.2) return "pediment"; if (y >= 17 && y <= 21 && x >= 5 && x <= 58) return y >= 20 && (x < 7 || x > 56) ? null : "entablature"; if (y >= 54 && y <= 57 && x >= 9 && x <= 54) return "steps"; if (y >= 58 && y <= 60 && x >= 5 && x <= 58) return "steps"; const inCol = (a) => (y >= 22 && y <= 24 && x >= a - 2 && x <= a + 6) || (y >= 50 && y <= 53 && x >= a - 2 && x <= a + 6) || (y >= 25 && y <= 49 && x >= a && x <= a + 4); if (inCol(10) || inCol(49)) return "columns"; if (y >= 22 && y <= 53 && x >= 15 && x <= 48) { if (x >= DX0 && x <= DX1 && y >= DY0) return null; // doorway: interior / leaves return "wall"; } return null;}function finialTone(x, y) { if (y === 5) return x === 30 || x === 33 ? null : x === 31 ? "n3" : "n2"; const i = litIndex(x, y, 32, 2.7, 2.6, 2.6, 4, 0.1); if (i < 0) return null; return x === 31 && y === 1 ? "n5" : STONE[i + 1];}function pedTone(x, y) { const dv = y + 0.5 - pedEdge(x), left = x < 32; if (dv < 1.1) return left ? "n4" : "n3"; // top of the raking cornice if (dv < 2.4) return left ? "n3" : "n2"; // its front fascia if (dv < 3.3) return "n0"; // shadow cast into the recessed tympanum if (y === 16) return "n0"; return (x + y * 3) % 17 === 0 ? "n0" : "n1"; // tympanum, a few pits}function entTone(x, y) { if (y === 17) return x < 8 || x > 55 ? "n4" : x < 32 ? "n4" : "n3"; if (y === 18) return x === 5 ? "n4" : x === 58 ? "n2" : "n3"; if (y === 19) return x < 7 || x > 56 ? "n1" : (x & 1) ? "n1" : "n3"; // dentils if (y === 20) return x === 7 ? "n4" : "n3"; return x === 56 ? "n1" : "n2";}function colTone(x, y) { const a = x < 32 ? 10 : 49, u = x - a; if (y <= 24) { // capital: abacus + echinus if (y === 22) return u === -2 ? "n4" : u === 6 ? "n2" : "n3"; if (y === 23) return u <= 0 ? "n4" : u <= 3 ? "n3" : "n2"; return u < -1 || u > 5 ? null : u <= 1 ? "n2" : "n1"; } if (y >= 50) { // base: torus + plinth if (y === 50) return u < -1 || u > 5 ? null : u <= 1 ? "n4" : u <= 3 ? "n3" : "n2"; if (y === 51) return u <= -1 ? "n3" : u <= 3 ? "n2" : "n1"; if (y === 52) return u === -2 ? "n4" : u === 6 ? "n1" : "n3"; return u === 6 ? "n0" : "n2"; } let k = ["n3", "n4", "n3", "n2", "n1"][u]; if (y === 25) k = dim(k, 1); // shadow under the capital if (u === 2 && (y % 6 === 3)) k = "n2"; // weathered flute marks return k;}function wallTone(x, y) { if (y === 22) return "n0"; // deep shadow under the frieze if (y === 23) return "n1"; if (x === 15 || (x === 16 && y > 24)) return "n1"; // the left column's shadow // door surround: jambs and lintel with a keystone if (y >= 25 && y <= 30 && x >= 29 && x <= 34) { if (y === 25) return x === 29 ? "n4" : x === 34 ? "n2" : "n4"; return x === 29 ? "n4" : x === 34 ? "n1" : "n3"; } if (y >= 27 && y <= 30 && x >= 19 && x <= 44) { if (y === 27) return x === 44 ? "n2" : "n4"; if (y === 30) return x >= DX0 && x <= DX1 ? "n1" : "n2"; return x === 19 ? "n4" : x === 44 ? "n1" : "n3"; } if (y >= 31 && (x === 20 || x === 21 || x === 42 || x === 43)) { if (x === 20 || x === 42) return "n4"; return "n2"; } if (y >= 31 && (x === 19 || x === 44)) return x === 19 ? "n2" : "n0"; // surround's shadowed return // ashlar: 4-row courses, staggered joints const r = Math.floor((y - 24) / 4), k = (y - 24) % 4; if (k === 3) return "n1"; const bx = x + (r & 1) * 3; if (bx % 7 === 0) return "n1"; const dark = hash(Math.floor(bx / 7), r) < 0.3; if (k === 0) return dark ? "n2" : "n3"; return dark ? "n1" : "n2";}function stepTone(x, y) { if (y === 54) return x === 9 ? "n3" : (x >= DX0 && x <= DX1) ? "n3" : "n2"; // back of the upper tread, sill if (y === 55) return x === 54 ? "n3" : "n4"; if (y === 56) return x === 9 ? "n3" : "n2"; if (y === 57) return "n1"; if (y === 58) return x < 9 || x > 54 ? (x === 5 ? "n4" : "n4") : "n3"; if (y === 59) return x === 5 ? "n3" : x === 58 ? "n1" : "n2"; return "n1";}const TONES = { finial: finialTone, pediment: pedTone, entablature: entTone, columns: colTone, wall: wallTone, steps: stepTone };function facade(g) { for (const name of ["finial", "pediment", "entablature", "wall", "columns", "steps"]) g.part(name, () => g.field({ x: [0, W - 1], y: [0, H - 1] }, (x, y) => (region(x, y) === name ? TONES[name](x, y) : null)));}function carvings(g) { // winged skull in the tympanum (relief: lit upper-left edges, shadowed lower-right) g.part("skull", () => { const S = [ "..####..", ".######.", "#XX##XX#", "#XX##XX#", ".##NN##.", "..TTTT..", ]; S.forEach((row, j) => [...row].forEach((c, i) => { const x = 28 + i, y = 10 + j; if (c === ".") return; let k; if (c === "X") k = (j === 2 && (i === 1 || i === 5)) ? "n0" : "ink"; else if (c === "N") k = i === 3 ? "n1" : "n0"; else if (c === "T") k = (i & 1) ? "n2" : "n4"; else k = (i <= 2 && j <= 1) || i === 0 ? "n5" : i >= 6 || j >= 4 ? "n3" : "n4"; g.pixel(x, y, k); })); for (const [x, y] of [[36, 12], [36, 13], [35, 14], [34, 15], [34, 16], [33, 16], [32, 16], [31, 16], [30, 16]]) g.pixel(x, y, "n0"); const wing = (s) => { // a swept wing of three feathers: lit top edge, relief face, hard shadow underneath const sx = (d) => s < 0 ? 27 - d : 36 + d; const rows = [[12, 0, 3], [13, 0, 6], [14, 0, 8], [15, 1, 7]]; for (const [y, d0, d1] of rows) for (let d = d0; d <= d1; d++) { let k = "n3"; if (y === 12 || (y === 13 && d >= 4) || (y === 14 && d >= 7)) k = s < 0 ? "n4" : "n3"; if (y === 15) k = d === 3 || d === 6 ? "n0" : "n1"; if (y === 14 && (d === 3 || d === 6) ) k = "n2"; g.pixel(sx(d), y, k); } g.pixel(sx(8), 15, "n0"); }; wing(-1); wing(1); }); g.part("weathering", () => { // rain streaks from the cornice, a crack, moss at the feet for (const [x, y0, n] of [[17, 24, 5], [46, 24, 3], [24, 24, 2]]) for (let i = 0; i < n; i++) g.pixel(x, y0 + i, i === n - 1 ? "n2" : "n1"); g.pixel(52, 30, "n1"); g.pixel(51, 31, "n1"); g.pixel(51, 32, "n2"); g.pixel(52, 33, "n1"); // crack in the right shaft g.pixel(45, 14, "n2"); // pits in the tympanum for (const [x, y, k] of [[8, 53, "e1"], [9, 53, "e2"], [10, 53, "e1"], [8, 52, "e1"], [47, 53, "e1"], [48, 53, "e2"], [9, 55, "e1"], [10, 54, "e2"], [11, 54, "e1"], [53, 56, "e1"], [54, 56, "e1"], [5, 59, "e1"], [6, 58, "e2"], [7, 58, "e1"], [57, 59, "e1"], [58, 59, "e1"], [16, 51, "e1"], [16, 52, "e2"], [15, 53, "e1"]]) g.pixel(x, y, k); g.pixel(40, 59, "n1"); g.pixel(41, 59, "n0"); g.pixel(19, 56, "n1"); // chips in the steps });}function sigil(g, pulse) { // a diamond rune cut into the keystone; the whole face tints violet at the peak g.part("sigil", () => { if (pulse > 0.75) for (const [x, y] of [[30, 26], [33, 26], [30, 30], [33, 30], [31, 26], [32, 30]]) g.pixel(x, y, "v0"); const rim = pulse > 0.6 ? "v2" : "v1", core = pulse > 0.75 ? "v3" : pulse > 0.35 ? "v2" : "v1"; g.pixel(31, 27, rim); g.pixel(32, 27, rim); g.pixel(30, 28, rim); g.pixel(33, 28, rim); g.pixel(31, 28, core); g.pixel(32, 28, core); g.pixel(31, 29, rim); g.pixel(32, 29, rim); if (pulse < 0.3) { g.pixel(31, 27, "v0"); g.pixel(32, 29, "v0"); } });}// ---- inside ---------------------------------------------------------------------------------------------------function interior(g, glow, ft, R) { // the tomb: black depth, a floor that catches the soulfire, an altar at the back wall const fx = 31.5, fy = 37; g.part("interior", () => g.field({ x: [DX0, DX1], y: [DY0, DY1] }, (x, y) => { const d = Math.hypot(x + 0.5 - fx, (y + 0.5 - fy) * 1.25); const r = R * glow; if (y >= 47) { // floor const fd = Math.hypot(x + 0.5 - fx, (y - 47) * 2.2); if (glow > 0.2 && fd < r * 0.8) return y === 47 ? "b1" : chk(x, y) ? "b0" : "n0"; if (glow > 0.2 && fd < r * 1.2) return chk(x, y) ? "n0" : "ink"; return "ink"; } if (glow > 0.05 && d < r * 0.45) return "b0"; if (glow > 0.05 && d < r * 0.75) return "v0"; if (glow > 0.05 && d < r * 0.95) return chk(x, y) ? "v0" : "ink"; return "ink"; })); if (glow > 0.2) g.part("altar", () => { { const k = glow > 0.6 ? "b1" : "n1"; g.rect({ x: [28, 35], y: [42, 42] }, k, { fill: k }); } g.rect({ x: [28, 35], y: [43, 46] }, "n0", { fill: "n0" }); g.line([28, 43], [35, 43], "b0"); g.pixel(28, 42, "n2"); g.pixel(35, 42, "n1"); }); if (glow > 0.35) g.detached("soulfire", () => { const h = Math.round((9 + 1.5 * Math.sin(TAU * ft)) * Math.min(1, glow * 1.2)); flame(g, 32, 41, 6, h, ft, 0.2, ["b1", "b2", "b3", "n5"]); flame(g, 29, 41, 3, Math.max(2, h - 4), ft, 0.65, ["b1", "b2", "b3", "n5"]); // a wisp drifting up const p = ft % 1; if (p < 0.7) g.pixel(31 + Math.round(Math.sin(TAU * p) * 2), 33 - Math.round(p * 3), p < 0.35 ? "b3" : "b2"); });}function leaves(g, th, lit) { // two leaves hinged at the jambs, swinging inward; the free edge rises as it recedes const a = th * Math.PI / 180; const w = Math.max(1, Math.round(LEAF * Math.cos(a))); const dy = RISE * Math.sin(a); const tex = (sx, ly, left) => { // closed texture at screen column sx, row ly const lx = left ? sx - DX0 : DX1 - sx; // 0 at the hinge if (ly === DY1) return "w0"; // strap hinges: two rows from the hinge, ending in a spear tip near the meeting stile for (const by of [35, 47]) { if (ly === by - 1 && lx === 7) return "n2"; if (ly === by + 2 && lx === 7) return "n0"; if ((ly === by || ly === by + 1) && lx <= 8) { if (lx === 8) return ly === by ? "n2" : "n0"; const top = ly === by; if (top && (lx === 1 || lx === 4)) return "n4"; if (!top && (lx === 1 || lx === 4)) return "n0"; return top ? (left ? "n3" : "n2") : "n1"; } } if (lx === LEAF - 1) return left ? "w1" : "w0"; // meeting stiles // ring pulls on round back-plates const rx = left ? 29 : 34, ry = 42, ddx = sx - rx, ddy = ly - ry; if (ddy === -3 && ddx === 0) return "n3"; if (ddy === -2 && Math.abs(ddx) <= 1) return ddx < 0 ? "n3" : ddx === 0 ? "n4" : "n1"; if (ddy >= -1 && ddy <= 2 && Math.abs(ddx) <= 1) { if (ddx === 0 && (ddy === 0 || ddy === 1)) return "w0"; if (ddx === -1) return ddy === 2 ? "n2" : "n4"; if (ddx === 1) return "n1"; return ddy === -1 ? "n4" : "n1"; } // planks: 3px each from the left edge of the doorway const p = (sx - DX0) % 3, pi = Math.floor((sx - DX0) / 3); let k = p === 0 ? "w1" : p === 1 ? "w3" : "w2"; if (!left && p === 1) k = "w2"; // the right leaf sits a touch darker if (p === 2 && ly % 7 === 3 && hash(pi, ly) < 0.5) k = "w1"; // a few grain marks if (sx === 31 || sx === 32) k = "w1"; if (ly <= DY0 + 2) k = dim(k, ly === DY0 ? 2 : 1); // the lintel's shadow if (ly >= DY1 - 1) k = dim(k, 1); return k; }; const leafDim = (left) => (left ? (th > 55 ? 2 : th > 25 ? 1 : 0) : (th > 45 ? 1 : 0)); g.part("doors", () => g.field({ x: [DX0, DX1], y: [DY0 - 1, DY1] }, (x, y) => { for (const left of [true, false]) { const i = left ? x - DX0 : DX1 - x; if (i < 0 || i >= w) continue; const sh = w > 1 ? Math.round(dy * i / (w - 1)) : 0; const ly = y + sh; if (y < DY0 || ly > DY1 || ly < DY0) continue; const lx = Math.min(LEAF - 1, Math.floor((i + 0.5) * LEAF / w)); const sx = left ? DX0 + lx : DX1 - lx; let k = dim(tex(sx, ly, left), leafDim(left)); if (th > 30 && i === w - 1) k = lit > 0.5 && (ly & 1) ? "b1" : "w0"; // free edge, rimmed by the soulfire if (th > 30 && ly === DY1) k = "ink"; return k; } return null; }));}// ---- fog ----------------------------------------------------------------------------------------------------------function puff(g, cx, cy, rx, ry, dens) { if (dens <= 0.02) return; for (let y = Math.floor(cy - ry); y <= Math.ceil(cy + ry); y++) for (let x = Math.floor(cx - rx); x <= Math.ceil(cx + rx); x++) { const nx = (x + 0.5 - cx) / rx, ny = (y + 0.5 - cy) / ry, r2 = nx * nx + ny * ny; if (r2 > 1) continue; const v = dens * (1 - r2); if (v > 0.5) g.pixel(x, y, "n5"); else if (v > thr(x, y) * 0.55) g.pixel(x, y, "n4"); }}function fogSill(g, t, amt) { // low wisps squeezing out under the doors and creeping along the sill if (amt <= 0) return; g.detached("fog", () => { for (const s of [-1, 1]) for (let k = 0; k < 3; k++) { const p = (t + k / 3 + (s > 0 ? 0.17 : 0)) % 1; const x = CX + s * (3 + p * 16), y = 54 + p * 2.5; puff(g, x, y, 3.2 + p * 3, 1.5 + p * 0.8, amt * Math.min(1, Math.sin(Math.PI * p) * 1.2)); } puff(g, CX, 54, 6 + Math.sin(TAU * t), 1.5, amt * (0.75 + 0.15 * Math.cos(TAU * t))); });}function fogSpill(g, t, amt) { // open: fog pours over the sill and rolls down the steps, spreading outwards if (amt <= 0) return; g.detached("fog", () => { for (let k = 0; k < 5; k++) { const p = (t + k / 5) % 1, s = (k & 1) ? 1 : -1; const x = CX + s * (1 + p * 17) + (k === 2 ? -3 : 0), y = 51.5 + p * 7.5; puff(g, x, y, 3.5 + p * 4, 1.7 + p * 1, amt * Math.min(1, Math.sin(Math.PI * p) * 1.3)); } puff(g, CX, 53, 8 + Math.sin(TAU * t) * 0.8, 2, amt * (0.7 + 0.12 * Math.cos(TAU * t))); });}function grit(g, f) { if (f < 2 || f > 6) return; g.detached("grit", () => { for (const [x, ph] of [[25, 0], [30, 1], [36, 0], [39, 2]]) { const y = DY0 + (f - 2 - ph) * 4; if (y >= DY0 && y <= DY1 - 1) { g.pixel(x, y, "n3"); if (y - 1 >= DY0) g.pixel(x, y - 1, "n1"); } } });}// ---- states -------------------------------------------------------------------------------------------------------function stateAt(state, t) { t = ((t % 1) + 1) % 1; const breathe = (tt) => 0.5 + 0.5 * Math.cos(TAU * tt); if (state === "closed") { const f = Math.round(t * N_IDLE) % N_IDLE, tt = f / N_IDLE; return { th: 0, glow: 0, pulse: breathe(tt), sill: 1, spill: 0, ft: tt, st: tt, R: 8, f: -1 }; } if (state === "open-idle") { const f = Math.round(t * N_IDLE) % N_IDLE, tt = f / N_IDLE; return { th: REST, glow: 1, pulse: breathe(tt), sill: 0, spill: 1, ft: tt, st: tt, R: 8 + 1.2 * Math.cos(TAU * tt), f: -1 }; } const f = Math.min(N_OPEN - 1, Math.round(t * N_OPEN)); if (f === 0) return stateAt("closed", 0); if (f === N_OPEN - 1) return stateAt("open-idle", 0); const tt = (((f - (N_OPEN - 1)) / N_IDLE) % 1 + 1) % 1; // run the idle clock backwards from the end const k = Math.min(1, f / 7); const glow = Math.max(0, Math.min(1, (f - 2) / 6)); return { th: OPEN_TH[f], glow, pulse: 1, sill: 1 - k, spill: k, ft: tt, st: f / N_IDLE, R: 9.2, f, flare: f <= 3 };}const Crypt = sprite(pal, (g, t, state) => { const S = stateAt(state, t); contactShadow(g, 32, GROUND, 28, 2); g.outlined("ink", () => { facade(g); interior(g, S.glow, S.ft, S.R); leaves(g, S.th, S.glow); }); carvings(g); sigil(g, S.pulse); if (S.flare) g.detached("flare", () => { for (const [x, y] of [[28, 27], [35, 27], [31, 24], [32, 32]]) g.pixel(x, y, S.f === 2 ? "v3" : "v2"); }); if (S.glow > 0.5) g.part("spill", () => { // soulfire light on the sill for (let x = 27; x <= 36; x++) if (chk(x, 54) || Math.abs(x - 31.5) < 2) g.pixel(x, 54, "b1"); }); if (S.f > 0) grit(g, S.f); fogSill(g, S.st, S.sill); fogSpill(g, S.ft, S.spill);});submit(Crypt, { size: [W, H], origin: "top-left", kind: "icon", scale: 1, animate: { fps: 10, duration: N_IDLE / 10, loop: true }, anchor: { x: 32, y: GROUND }, args: ["closed"], variants: [ { name: "closed", args: ["closed"] }, { name: "open", args: ["open"], animate: { fps: 10, duration: N_OPEN / 10, loop: false } }, { name: "open-idle", args: ["open-idle"] }, ],});掲載されたコードでは、ビルドスクリプトが自動付与する共通部分を省略しています。具体的には 1 行のランタイムヘッダー、共有パレット、および汎用ヘルパー関数(contactShadow、litIndex、flame、hash、chk、TAU)です。表示されている行番号は実際のファイルと一致しています。これらのプログラムは AIエージェントが生成し、描画されたコマの仕上がりを確認しながら修正を加えたものです。
エージェントに、こうしたプログラムを書かせる。
Tessarune は描画 API のリファレンス、25 冊の制作マニュアル、結果を確かめるための描画エンジンをエージェントに渡します。