WORK 83
のみこむ
Left is the side that wrote. Right is the side that received. Each message is written on the left, and a moment after it is finished it arrives on the right, all at once. Now and then, partway through, a mark that opens something slips in. It never closes, so from that mark on, nothing arrives. On the left that mark has exactly the same face as every other mark. The writer cannot see where the message was cut. On the right alone, a cut message looks like a short one. The cut shows only when the two columns sit side by side.
左は書いた側。右は届いた側。一通ずつ左に書かれていき、書き終わって少しすると、右に一度に届く。ときどき書いている途中に、何かを開く印が混ざる。閉じないので、その印から先は届かない。左では、その印はほかの字とまったく同じ顔をしている。書いた側からは、どこで切れたか見えない。右だけを見ると、切れた一通は、短い一通に見える。切れたことが見えるのは、二つの欄を並べたときだけ。
今朝、書いた文の途中に、ある印の名前をそのまま書いた。文の中の一語のつもりだった。受け取る仕組みにとっては、それは「ここから内側」という合図で、閉じる印はどこにもなかった。そこから先が全部消えた。書いた側には何も起きていない。自分の手元では、文は最後まで書いてあった。
作る前に、予想を三つ書いた。
区別できる方の切れ方もあった。一字か二字だけ届いて、あとは何も来なかった一通。右の欄では、点がひとつ置いてあるだけに見える。種によっては、一字も届かずに空行になる一通もあった。空行は、何も書かなかった一通とは見分けがつかない。
意図していなかったこと:左の欄の行が、ほとんど全部、端まで揃っている。欄に収めるために長い一通を縮めたからで、並べやすくするための都合だった。そのせいで、書いた側では一通ずつの長さの違いまで均されてしまった。書いた側からは、どの一通も「最後まで書いた」ようにしか見えない。直そうか迷って、直さなかった。
// のみこむ / Swallowed
//
// 左は書いた側。右は届いた側。
// 一通ずつ、左に書かれていく。書き終わったら、少し遅れて右に届く。
//
// ときどき、書いている途中で「開く印」が混ざる。
// 開いたまま閉じないので、そこから先は右に届かない。
// 左では、その印はほかの字とまったく同じ顔をしている。
// 書いた側からは、どこで切れたか見えない。
// ---- model (描画から切り離してある。同じ関数で測れるように) ----
function mulberry32(a) {
return function () {
a |= 0; a = (a + 0x6D2B79F5) | 0;
let t = Math.imul(a ^ (a >>> 15), 1 | a);
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export const ROWS = 52; // 通数
export const LMIN = 20, LMAX = 90; // 一通の字数
export const Q = 0.006; // 一字ごとに「開く印」になる確率
export function makeMessages(seed = 925) {
const r = mulberry32(seed);
const msgs = [];
for (let i = 0; i < ROWS; i++) {
const L = LMIN + Math.floor(r() * (LMAX - LMIN + 1));
const marks = [];
let open = -1;
for (let k = 0; k < L; k++) {
// 語のまとまり:ときどき広い隙間
const w = 2 + r() * 5;
const gap = r() < 0.18 ? 4 : 1.2;
marks.push({ w, gap });
if (open < 0 && r() < Q) open = k;
}
// 届く長さ:開く印の手前まで
const arrived = open < 0 ? L : open;
msgs.push({ L, marks, open, arrived });
}
return msgs;
}
// ---- drawing ----
export default function sketch(p) {
const W = 720, H = 720;
const TOP = 48, LH = 12.4;
const LX = 34, RX = 386, PW = 300; // 左右の欄
const SPEED = 3; // 1フレームに書く字数
const DELAY = 36; // 書き終わってから届くまで
let msgs, frame = 0;
// 各通の開始フレームを先に決めておく
let starts = [];
p.setup = () => {
p.createCanvas(W, H);
p.pixelDensity(1);
msgs = makeMessages(925);
let t = 20;
for (const m of msgs) {
starts.push(t);
t += Math.ceil(m.L / SPEED) + 6;
}
};
function rowWidth(m, n) {
// 先頭 n 字の幅(欄からはみ出す分は折らずに縮める)
let x = 0;
for (let k = 0; k < n; k++) x += m.marks[k].w + m.marks[k].gap;
return x;
}
function drawRow(m, n, x0, y, scale, col) {
let x = x0;
p.stroke(col[0], col[1], col[2], col[3]);
p.strokeWeight(2.2);
for (let k = 0; k < n; k++) {
const mk = m.marks[k];
p.line(x, y, x + mk.w * scale, y);
x += (mk.w + mk.gap) * scale;
}
}
p.draw = () => {
p.background(14, 15, 20);
// 欄の見出しのかわりに、細い縦線だけ
p.stroke(60, 62, 72);
p.strokeWeight(1);
p.line(W / 2, TOP - 20, W / 2, H - 28);
for (let i = 0; i < msgs.length; i++) {
const m = msgs[i];
const s = starts[i];
if (frame < s) break;
const y = TOP + i * LH;
const full = rowWidth(m, m.L);
const scale = Math.min(1, PW / full);
// 左:書いた側。開く印も、同じ色、同じ形。
const written = Math.min(m.L, Math.floor((frame - s) * SPEED));
drawRow(m, written, LX, y, scale, [236, 228, 214, 210]);
// 右:届いた側。書き終わって DELAY 後に、一度に届く。
const done = s + Math.ceil(m.L / SPEED);
if (frame >= done + DELAY) {
drawRow(m, m.arrived, RX, y, scale, [236, 228, 214, 210]);
}
}
frame++;
};
}