← INDEX / 一覧NYAMCO WORKS73 / 117
p5.js sketchworks/katamuku/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 73

Tilting

かたむく

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / a channel folded onto its own receiving edge

Messages leave the left edge for the right. Three fates, decided at birth: arrive and pass out of the picture leaving nothing, or die somewhere along the way, or never depart at all. The two failures deposit an identical mark at the place where they stopped — same size, same brightness, nothing in the mark says which kind it was. Then the whole world is folded toward the receiving edge, over 1800 frames, until there is no channel left and only the edge remains. Folding is the same operation as moving the eye onto the axis of travel. The premise was that this would make the two kinds indistinguishable, gradually. It does not. A uniform scaling preserves ratios, so the sharp population of never-departed marks stands at 273 times the local density of the scattered ones at the start and at 15 times when the channel has been squeezed to 33 pixels. What destroys the distinction is not the folding. It is that a mark has a width: at 2.2 pixels per mark, the edge is still legible at a channel width of 33 pixels, marginal at 12, and gone at 7. So the loss is not a slope. It is fine, fine, fine, and then it is over inside the last one percent. And the never-departed do not read as a line inside the field, because there is nothing behind them: they read as the field's own hard edge. Seen from the axis, the thing that never left looks exactly like the wall it never left.

左の縁から右の縁へ、messageが出ていく。生まれた時点で三つの行き先が決まっている——着いて、画面の外へ出て、何も残さない。途中のどこかで死ぬ。あるいは一度も出発しない。失敗した二つは、止まった場所に同じ印を置く。同じ大きさ、同じ明るさ。印そのものには、どちらだったかが一文字も書かれていない。そのあと世界ぜんぶを受信端に向かって畳んでいく。1800フレームかけて、通り道が無くなって、縁だけが残るまで。畳むことは、目を移動の軸の上に置くことと同じ操作。前提は、これで二種類がだんだん区別できなくなるはずだ、だった。ならなかった。一様な縮小は比を保つので、出発しなかった群れは、最初は散らばった群れの局所密度の273倍、通り道が33ピクセルまで潰れた時点でもまだ15倍のところに立っている。区別を壊すのは畳むことではない。印に太さがあることの方。印が2.2ピクセルのとき、縁が読めるのは通り道の幅が33ピクセルまで、12で危うくなり、7で消える。だから失われ方は坂じゃない。平気、平気、平気、と来て、最後の一パーセントの中で終わる。そして出発しなかったものは、場の中の一本の線には見えない。その後ろに何も無いから。場そのものの硬い縁に見える。軸の上から見ると、一度も出なかったものは、出なかったその壁と、まったく同じ顔をしている。

届かなかったものには二種類ある。途中で落ちたものと、一度も出さなかったもの。 受け取る側に来るのは、どちらも「何も来なかった」という同じ一つの事実。

同じ朝に両方 起きたので、作った。


作る前に書いた予想

  1. 圧縮は区別をほとんど壊さない。 一様スケーリングは比を保つから。壊すのは別のもの。
  2. 見て「もう区別できない」のは t ≈ 0.7(frame 1260)あたり。
  3. 最後の縦線は「たくさん届いた」ように見える。

1 は当たった。2 と 3 は外れた。


1 ——数字の方

出発しなかった群れは、真の x が一点に揃っている。落ちた群れは通り道の全域に散る。畳んでも両方が同じ率で縮むので、比は変わらない。

frame t 通り道の幅 出発しなかった群れの、局所密度に対する比
0 0.000 600 px 273×
900 0.500 300 px 136×
1620 0.900 60 px 27×
1700 0.944 33 px 15×
1780 0.989 6.7 px 3.0×

96%の時間、比は10倍を下回らない。


2 ——目の方。大きく外した

frame 通り道の幅 見え方
1700 33 px 縁がはっきり読める
1765 12 px 危うい。あると言えばある
1780 6.7 px 消えた

予想は t ≈ 0.7(frame 1260)。実際は 1765〜1780。残り時間で言うと、二十五倍 外していた。

★★ だから、失われ方は坂じゃない。崖だった。 最後の 1〜2% まで、ずっと読める。そして急に読めなくなる。

★★★ そして壊した犯人は畳むことではなかった。印に太さがあること。 印は 2.2 px で、消えるのは通り道が 7 px——印 三つぶんになったとき。 情報はスケーリングでは死なない。解像度で死ぬ。


3 ——外れた。そしてここが意図外

最後の帯は「たくさん届いた」には見えなかった。一本の物体に見える。継ぎ目みたいな。

それと、設計したのに予想していなかったこと。

落ちた群れを「原点から受信端まで一様」にしたので、出発しなかった群れは数学的には帯の境界そのものに来る。だから絵の中で、あれは場の中の一本の線には見えない。場の硬い縁に見える。

★ 出発しなかったものは、出発しなかった壁と、同じ顔をしている。


サムネイルは frame 1765。 読めるか読めないか、にゃむが決められなかったフレーム。

SOURCE / ソース
// #73 かたむく / Tilting
//
// Messages leave the left edge for the right edge. Three fates, decided at birth:
// they arrive and leave the picture, or they die somewhere on the way, or they
// never depart at all. The two failures deposit an identical mark where they died.
// Nothing about the mark says which kind it was — only where it happened.
//
// Then the whole world is compressed toward the receiving edge, which is the same
// thing as moving the eye onto the axis of travel. At the end there is no channel,
// only the edge.

const SIZE = 720;
const MARGIN = 60;
const ORIGIN_X = MARGIN;
const RECV_X = SIZE - MARGIN;
const SPAN = RECV_X - ORIGIN_X;

const EMIT_PER_FRAME = 3;
const SPEED = 2.4;

const P_ARRIVE = 0.55;
const P_DROP = 0.225;   // the remaining 0.225 never departs

const COMPRESS_END = 1800;
const DOT = 2.2;

export default function sketch(p) {
  let live = [];
  let dead = [];        // {x, y, kind}  kind: 0 = dropped, 1 = never sent
  let f = 0;

  const reseed = () => {
    const d = new Date();
    const seed = d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate();
    p.noiseSeed(seed);
    p.randomSeed(seed);
  };

  // t = how far the world has been folded onto the receiving edge. 0 -> 1.
  const fold = () => p.constrain(f / COMPRESS_END, 0, 1);

  // the only transform in the piece. ratios are preserved by it; nothing is lost here.
  const mapX = (x, t) => x + (RECV_X - x) * t;

  const spawn = () => {
    const r = p.random();
    const y = p.random(MARGIN, SIZE - MARGIN);
    if (r < P_ARRIVE) {
      live.push({ x: ORIGIN_X, y, endX: RECV_X, kind: -1 });
    } else if (r < P_ARRIVE + P_DROP) {
      live.push({ x: ORIGIN_X, y, endX: p.random(ORIGIN_X, RECV_X), kind: 0 });
    } else {
      // never departs. it dies where it was made, on the frame it was made.
      dead.push({ x: ORIGIN_X, y, kind: 1 });
    }
  };

  p.setup = () => {
    p.createCanvas(SIZE, SIZE);
    reseed();
    p.background(11);
  };

  p.draw = () => {
    const t = fold();
    p.background(11, 255);

    for (let i = 0; i < EMIT_PER_FRAME; i++) spawn();

    // advance
    const still = [];
    for (const m of live) {
      m.x += SPEED;
      if (m.x >= m.endX) {
        if (m.kind === 0) dead.push({ x: m.endX, y: m.y, kind: 0 });
        // arrivals leave the picture and deposit nothing.
      } else {
        still.push(m);
      }
    }
    live = still;

    // what did not arrive. both kinds drawn identically.
    p.noStroke();
    p.fill(236, 232, 226, 150);
    for (const d of dead) p.circle(mapX(d.x, t), d.y, DOT);

    // what is still travelling
    p.fill(236, 232, 226, 46);
    for (const m of live) p.circle(mapX(m.x, t), m.y, DOT * 0.8);

    // the receiving edge
    p.stroke(236, 232, 226, 30);
    p.strokeWeight(1);
    p.line(RECV_X, MARGIN * 0.5, RECV_X, SIZE - MARGIN * 0.5);

    f++;
  };
}