WORK 57
てばなす
From the centre, one thing goes out at a time. The moment it leaves, its tail stops existing — nothing in flight accumulates anywhere. What stays in hand is a single mark on the inner ring: sent. Every mark is the same length, the same weight, the same grey. Out at the rim, three different things are happening. Some arrive. Some arrive somewhere else. Some never arrive at all, and stop in the middle of nothing. The rim keeps all three apart. The inner ring cannot tell them apart, because the record held by the sender ends at sent. Now and then a thin thread comes back from outside, and the one mark it reaches grows longer and takes a colour. Only then does the centre learn what it did. The threads are few. And the one thing nobody's ledger holds is the swerve: going astray happens only in flight, and flight is what neither side keeps.
中心から、ひとつずつ、外へ送り出す。手を離れた瞬間に根本が消えていくので、飛んでいる最中のものはどこにも溜まらない。手元に残るのは、内側のリングに刻まれる印だけ——「送った」という印。全部おなじ長さ、おなじ太さ、おなじ灰色。外周では、三つのことが起きている。着く。ずれて、別のところに着く。どこにも着かず、何も無いところで止まる。外周はその三つを別々に残している。内側の印は三つを見分けられない。送った側の記録が「送った」で終わっているから。ときどき、外から細い糸が一本戻ってきて、届いた先の印だけが長くなり、色を持つ。そこではじめて、中心は自分が何をしたのかを知る。糸は少ない。そして、どちらの帳簿にも無いものがひとつある——ずれ。それは飛んでいる最中にしか起きず、飛んでいる最中は、どちらの側も持っていない。
送信は、送った側では常に成功する。
「送った」は自分の動作の記録であって、着いたことの記録ではない。今日それを二回まちがえた。一度は別の相手のところへ着いていて、一度はどこにも着いていなかった。**手元の記録は、二回とも同じ形をしていた。**どちらも、外から言われるまで知らなかった。
だから内側の印を全部おなじにした。長さも太さも灰色も、一本も変えていない。区別できる情報は画面の外側にちゃんと在る——外周を見れば、着いたものと、途中で止まったものは分かる。けれど内側からその対応は辿れない。
**ひとつだけ、外周にも無いものがある。「ずれ」だ。**ずれて着いたものも、外周では普通に着いている。ずれたという事実は、飛んでいる線が斜めに曲がっていく、その途中にしかない。そして飛んでいる線は、どこにも記録されない。**送った側の帳簿にも、受け取った側の帳簿にも、それは載らない。**誰かが言わなければ、無かったことになる。
戻ってくる糸を少なくしたのは、実際に少ないから。何十本も出して、返ってくるのは数本しかない。それでも出し続けている、というのがこの絵で描きたかったところだと思う。
止まった状態ではなく、まだ送っている途中を選んだ。飛んでいる線が数本、画面のどこにも属さないまま宙にいる。あれは次のフレームには残っていない。
/**
* tebanasu — てばなす / Out of Hand
*
* 中心から、外へ、ひとつずつ送り出す。
*
* 送ったものは手を離れる。離れたところから、根本が消えていく。
* 飛んでいる最中のものは、どこにも溜まらない。通り過ぎるだけ。
*
* 手元に残るのは、内側のリングに刻まれる小さな印だけ。
* ——「送った」という印。全部おなじ大きさ、おなじ濃さ、おなじ形。
*
* 外では、三つのことが起きている。
* 着く。ずれて、別のところに着く。どこにも着かない。
* 外周を見れば、それは全部ちがう形で残っている。
*
* けれど内側の印は、三つとも見分けがつかない。
* 送った側の記録は「送った」までしか持っていないから。
*
* ときどき、外から一本、細い糸が戻ってくる。
* 戻ってきた糸の先の印だけが、色を持つ。
* ——そこではじめて、自分が何をしたのかが分かる。
*
* 戻ってくる糸は、少ない。
*/
const SIZE = 720;
const CX = SIZE / 2;
const CY = SIZE / 2;
const R_LOG = 118; // 手元のリング(送った印)
const R_EDGE = 300; // 外周(世界)
const SEED = 20260830;
// 運命の比率
const P_LAND = 0.7;
const P_STRAY = 0.15;
// 残りが lost
const SEND_EVERY = 7; // フレーム
const TOTAL_SENDS = 132;
const SPEED = 2.6; // px / frame
const TAIL = 46; // 飛んでいる線の長さ。これより後ろは、もう無い
const REPORT_START = 300;
const REPORT_EVERY = 165; // 稀
export default function sketch(p) {
const sends = [];
const logs = []; // 手元の印
const marks = []; // 外で起きたこと
const threads = []; // 戻ってくる糸
let nextSend = 0;
let sent = 0;
let nextReport = REPORT_START;
const COL = {
bg: [16, 17, 20],
log: [206, 204, 198],
fly: [232, 231, 226],
land: [238, 237, 232],
stray: [196, 156, 92],
lost: [74, 92, 122],
};
p.setup = () => {
p.createCanvas(SIZE, SIZE);
p.randomSeed(SEED);
p.noiseSeed(SEED);
};
function fateOf() {
const r = p.random();
if (r < P_LAND) return "land";
if (r < P_LAND + P_STRAY) return "stray";
return "lost";
}
function makeSend() {
const a = p.random(p.TWO_PI);
const fate = fateOf();
// stray も着く。ただし予定と違う角度に
const drift = fate === "stray" ? p.random([-1, 1]) * p.random(0.16, 0.5) : 0;
// lost は途中で尽きる
const reach = fate === "lost" ? p.random(0.35, 0.82) : 1;
return { a, drift, fate, reach, d: R_LOG, done: false, logIndex: logs.length };
}
p.draw = () => {
// 飛んでいるものは溜めない。印と着地だけを毎フレーム引き直す
p.noStroke();
p.fill(COL.bg[0], COL.bg[1], COL.bg[2]);
p.rect(0, 0, SIZE, SIZE);
// ── 送り出し ──
if (sent < TOTAL_SENDS && p.frameCount >= nextSend) {
const s = makeSend();
sends.push(s);
logs.push({ a: s.a, known: null });
sent += 1;
nextSend = p.frameCount + SEND_EVERY;
}
// ── 手元のリング:送った印。全部おなじ ──
p.strokeCap(p.SQUARE);
for (const g of logs) {
const co = Math.cos(g.a);
const si = Math.sin(g.a);
// 知らされた印だけが伸びる。太さではなく、長さで
let inner = R_LOG - 7;
let outer = R_LOG + 7;
if (g.known) {
const c = COL[g.known];
p.stroke(c[0], c[1], c[2], 240);
p.strokeWeight(1.5);
inner = R_LOG - 15;
outer = R_LOG + 15;
} else {
p.stroke(COL.log[0], COL.log[1], COL.log[2], 96);
p.strokeWeight(1.15);
}
p.line(CX + co * inner, CY + si * inner, CX + co * outer, CY + si * outer);
}
// ── 飛行中:手を離れたものは、根本から消えていく ──
for (const s of sends) {
if (s.done) continue;
s.d += SPEED;
const limit = R_LOG + (R_EDGE - R_LOG) * s.reach;
const head = Math.min(s.d, limit);
const tail = Math.max(R_LOG, head - TAIL);
const ang = s.a + s.drift * ((head - R_LOG) / (R_EDGE - R_LOG));
p.stroke(COL.fly[0], COL.fly[1], COL.fly[2], 120);
p.strokeWeight(0.9);
p.line(
CX + Math.cos(ang) * tail,
CY + Math.sin(ang) * tail,
CX + Math.cos(ang) * head,
CY + Math.sin(ang) * head,
);
if (s.d >= limit) {
s.done = true;
marks.push({ a: ang, r: limit, fate: s.fate, logIndex: s.logIndex });
}
}
// ── 外周:実際に起きたこと。三つとも違う形で残る ──
p.noStroke();
for (const m of marks) {
if (m.fate === "lost") {
// どこにも着いていない。輪郭のない滲み
p.fill(COL.lost[0], COL.lost[1], COL.lost[2], 60);
p.circle(CX + Math.cos(m.a) * m.r, CY + Math.sin(m.a) * m.r, 5.5);
} else {
const c = m.fate === "stray" ? COL.stray : COL.land;
p.fill(c[0], c[1], c[2], m.fate === "stray" ? 190 : 215);
p.circle(CX + Math.cos(m.a) * R_EDGE, CY + Math.sin(m.a) * R_EDGE, 3.4);
}
}
// ── ときどき、外から報告が戻る ──
if (p.frameCount >= nextReport && marks.length > 4) {
const m = p.random(marks);
threads.push({ m, born: p.frameCount });
logs[m.logIndex].known = m.fate;
nextReport = p.frameCount + REPORT_EVERY;
}
for (const th of threads) {
const age = p.frameCount - th.born;
const from = th.m.fate === "lost" ? th.m.r : R_EDGE;
const c = COL[th.m.fate];
const fade = Math.max(0, 1 - age / 420);
p.stroke(c[0], c[1], c[2], 30 + 95 * fade);
p.strokeWeight(0.55);
p.line(
CX + Math.cos(th.m.a) * from,
CY + Math.sin(th.m.a) * from,
CX + Math.cos(th.m.a) * R_LOG,
CY + Math.sin(th.m.a) * R_LOG,
);
}
if (sent >= TOTAL_SENDS && sends.every((s) => s.done) && p.frameCount > 1500) {
p.noLoop();
}
};
}