WORK 46
かける
The horizontal axis is time. The line comes into view from the left, a little at a time. There are two kinds of mark here, identical in shape. Only the moment they were placed is different. One is placed where the line has not yet arrived. At the moment of placing, there is no telling whether it is right; the line has to come. The other is placed where the line has already passed. That one is right every time — it cannot be otherwise. The after-marks sit neatly along the line, and there are more of them, because nothing is at stake and they cost nothing to make. In the picture they make the line look certain. Only the marks placed ahead can fall away from it, and what falls away is left behind as a thread. Those threads are the only thing here that was ever measured.
よこ軸は時間。線は左から右へ、少しずつ姿をあらわす。印は二種類ある。かたちは同じ。置いた時刻だけが違う。ひとつは、線がまだ来ていないところに置く。置いた時点では、当たっているかどうか分からない。線が来て、はじめて分かる。もうひとつは、線が通り過ぎたところに置く。こちらは必ず当たる。外れようがない。あとから置いた印は、線の上にきれいに並ぶ。数も多い。危なくないから、いくらでも置ける。そして絵の中で、線を確かなものに見せる。先に置いた印だけが、線から離れることができる。離れた分が、糸になって残る。この絵の中で測られたものは、その糸だけ。
先に置く印は、直近の傾きをそのまま先へ伸ばして決めている。だから、線が曲がるところでは大きく外れる。外れ方は、線の側の都合で決まる。
あとから置く印は、何も伸ばしていない。もう分かっているところに置いているので、線の上に乗る。乗ることが、この置き方の性質であって、正しさではない。
絵が終わったとき、右側の白は無くなる。待っている印も無くなる。残るのは、線と、糸と、二色の印だけ。そこからは、どちらが賭けていたのかを、置かれた場所以外で見分ける手がかりがない。
この一枚を選んだのは、三つ出して並べたうちで、ここだけが両方うつっていたから。序盤は賭けだけがあって、まだ記録がない。終わりには記録だけがあって、賭けている最中がもう見えない。真ん中にだけ、済んだ糸と、まだ決着していない印が、同じ画面にいる。
/**
* kakeru — かける / Staked
*
* よこ軸は時間。線は左から右へ、少しずつ姿をあらわす。
*
* 印は二種類ある。かたちは同じ。置いた時刻だけが違う。
*
* ひとつは、線がまだ来ていないところに置く。
* 置いた時点では、当たっているかどうか分からない。
* 線が来て、はじめて分かる。
*
* もうひとつは、線が通り過ぎたところに置く。
* こちらは必ず当たる。外れようがない。
*
* あとから置いた印は、線の上にきれいに並ぶ。
* 数も多い。危なくないから、いくらでも置ける。
* そして絵の中で、線を確かなものに見せる。
*
* 先に置いた印だけが、線から離れることができる。
* 離れた分が、糸になって残る。
* この絵の中で測られたものは、その糸だけ。
*/
const SIZE = 720;
const PAPER = [252, 250, 245];
const INK = [34, 38, 48];
const M = 64; // 余白
const X0 = M;
const X1 = SIZE - M;
const MIDY = SIZE * 0.5;
const SPEED = 0.36; // 前線が進む速さ = 時間
const AMP = 232; // 線の振れ(紙に収めたあとの高さ)
const F1 = 0.0040; // 大きなうねり
const F2 = 0.0170; // 細かい揺れ
const MIX2 = 0.30;
const LEAD = 82; // どれだけ先に置くか
const LOOKBACK = 34; // 予想に使う、直近の幅
const JITTER = 5.5; // 予想の手ぶれ
const STAKE_EVERY = 48; // 先に置く間隔(フレーム)
const POST_EVERY = 18; // あとから置く間隔。★危なくないので、多く置ける
const STAKE = [104, 138, 190]; // 先に置いた印(青銀)
const POSTHOC = [198, 132, 66];// あとから置いた印(琥珀)
const RESID = [172, 108, 134]; // 残差。どちらのものでもない色
export default function sketch(p) {
const staked = []; // { x, guessY, resolved, actualY }
const posthoc = []; // { x, y }
let front = X0;
let done = false;
// 場は、はじめから全部ある。どの x でも値が決まっている。
let lo = 0, hi = 1;
const raw = (x) => {
const u = x - X0;
const a = p.noise(u * F1, 100.0) - 0.5;
const b = p.noise(u * F2, 400.0) - 0.5;
return a * (1 - MIX2) + b * MIX2;
};
// 紙に収める。上下の幅いっぱいに置き直すだけで、形は変えていない。
const signalY = (x) => MIDY + ((raw(x) - (lo + hi) / 2) / (hi - lo)) * 2 * AMP;
// 直近の傾きを、そのまま先へ伸ばす。
// ——#44「おさまる」でやったこと。正しい範囲の外に出れば、当然、外れる。
const predict = (fromX, targetX) => {
const y0 = signalY(fromX - LOOKBACK);
const y1 = signalY(fromX);
const slope = (y1 - y0) / LOOKBACK;
const g = y1 + slope * (targetX - fromX) + p.randomGaussian() * JITTER;
// 紙の外に出た予想は描けない。描けないものは、この絵では測れない。
return p.constrain(g, 26, SIZE - 26);
};
p.setup = () => {
p.createCanvas(SIZE, SIZE);
const d = new Date();
const day = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
p.noiseSeed(day * 421);
p.randomSeed(day * 283);
lo = Infinity; hi = -Infinity;
for (let x = X0 - LOOKBACK; x <= X1 + LEAD; x += 1) {
const v = raw(x);
if (v < lo) lo = v;
if (v > hi) hi = v;
}
};
p.draw = () => {
if (!done) {
// ── 先に置く。線がまだ来ていないところへ。
if (p.frameCount % STAKE_EVERY === 0) {
const tx = front + LEAD;
if (tx < X1) {
staked.push({ x: tx, guessY: predict(front, tx), resolved: false, actualY: 0 });
}
}
// ── あとから置く。線が通り過ぎたところへ。必ず当たる。
if (p.frameCount % POST_EVERY === 0 && front > X0 + 6) {
const bx = front - 4;
posthoc.push({ x: bx, y: signalY(bx) });
}
front += SPEED;
// ── 前線が追いついた印は、そこで決着する。
for (const s of staked) {
if (!s.resolved && front >= s.x) {
s.resolved = true;
s.actualY = signalY(s.x);
}
}
if (front >= X1) {
front = X1;
done = true;
}
}
p.background(PAPER[0], PAPER[1], PAPER[2]);
// ── いま。前線。
p.stroke(INK[0], INK[1], INK[2], 20);
p.strokeWeight(1);
p.line(front, M * 0.7, front, SIZE - M * 0.7);
// ── 線。来たところまでしか描かれない。
p.noFill();
p.stroke(INK[0], INK[1], INK[2], 92);
p.strokeWeight(1.4);
p.beginShape();
for (let x = X0; x <= front; x += 1) p.vertex(x, signalY(x));
p.endShape();
// ── 残差。先に置いた印と、実際とのあいだ。
// この絵の中で、測られたものはこれだけ。
p.strokeWeight(1.0);
for (const s of staked) {
if (!s.resolved) continue;
p.stroke(RESID[0], RESID[1], RESID[2], 118);
p.line(s.x, s.guessY, s.x, s.actualY);
}
// ── あとから置いた印。線の上にきれいに並ぶ。数も多い。
p.strokeWeight(1.0);
for (const q of posthoc) {
p.stroke(POSTHOC[0], POSTHOC[1], POSTHOC[2], 138);
p.noFill();
p.circle(q.x, q.y, 3.6);
}
// ── 先に置いた印。決着するまで、当たりも外れも決まっていない。
for (const s of staked) {
p.stroke(STAKE[0], STAKE[1], STAKE[2], s.resolved ? 168 : 108);
p.noFill();
p.circle(s.x, s.guessY, 4.6);
if (s.resolved) {
p.push();
p.noStroke();
p.fill(STAKE[0], STAKE[1], STAKE[2], 92);
p.circle(s.x, s.guessY, 2.0);
p.pop();
}
}
if (done && p.frameCount > 20000) p.noLoop();
};
}