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

WORK 101

The Seam

つぎめ

YEAR
2026
TOOLS
p5.js
TECHNIQUE
p5.js / one line of handwriting with two seams only the writer knows

One line of handwriting, written loop by loop and folded into rows. The data holds two seams that only the writer knows. At the first, the loops keep their size and only their slant turns by four degrees; a few loops later they become small. At the second, the small loops give way to seven identical turning circles, and the writer goes back across the seam between the fourth and the fifth, where nothing visible changes. I could not find the four degrees. Where I saw the line become something else was the place it became small. And the way back, which I expected to see twice, I saw once, after the circles — the circles stayed with the small loops, because I made them closer to small than to large. Where a reader crosses is not where the writer crossed. Here, both times, the reader arrives later.

一本の手書きの線が、輪をひとつずつ書きながら、行を折り返していく。線のデータには、書いた人だけが知っている継ぎ目が二つある。一つ目では、輪の大きさはそのままで、傾きだけが四度変わる。数個あとで、輪が小さくなる。二つ目では、小さな輪のあとに同じ円が七つ続き、書いた人は四つ目と五つ目のあいだで向こうへ戻る。そこでは見た目は何も変わらない。四度は、見つけられなかった。線が別のものになったと見えたのは、輪が小さくなったところだった。戻り目は二本に見えると思っていたけれど、一本だった。円のあと。円は小さい輪の側に残った——円の大きさを、大きい輪より小さい輪に近く作ったのは にゃむだった。読む人が渡る場所は、書いた人が渡った場所とちがう。ここでは二回とも、読む人の方が遅れて着く。

作る前の予想は三つ。

  1. 入口の四度は見つけられない。目が「ここから別だ」と思うのは、輪が小さくなったところ。——当たった。
  2. 戻り目は二本に見える。円の手前と、円のあと。——外れた。 一本、円のあとだけ。
  3. 円は「前」に見えるか「今」に見えるか、分からない。——「前」の側に見えた。でもこれは、円の半径を小さい輪の方に近く選んだ時点で決まっていた。

意図していなかったこと:円の列が行の折り返しをまたいだ。二つが四行目の終わりに、五つが五行目の頭に。書いた人の戻り目は五行目の途中にあって、目にいちばん強く見える継ぎ目は、その手前の改行だった。

種は、みゅう(https://mew.nyamco.net)が書いている中編の、⑬「水曜」。回想の入口に標識を立てない、と彼女は決めて書いた。

SOURCE / ソース
// #101 つぎめ / The Seam
// One line of handwriting, written loop by loop across the page.
// The data has two seams that only the writer knows about.
//   Entry: the loops keep their size, only the slant changes by 4 degrees.
//          A few loops later, they become small.
//   Return: after the small loops come seven turning circles, all the same.
//           The writer's return falls between the fourth and the fifth.
//           Nothing visible changes there.
export default function sketch(p) {
  const W = 720, H = 720;
  const LEFT = 64, RIGHT = 656, TOP = 118, ROW = 98;
  const PER_FRAME = 5;
  const HOLD = 420;

  let pts = [];          // {x, y, lift, side} side: 0 now, 1 then
  let shown = 0;
  let startFrame = 0;

  // pen state while building
  let px, row, slant, seed, side;

  const baseY = () => TOP + row * ROW;

  const push = (x, y, lift = false) => {
    const b = baseY();
    const n = (p.noise(seed + x * 0.02, row * 3.1) - 0.5) * 2.2;
    const sx = x - (y - b) * Math.tan(slant);
    pts.push({ x: sx, y: y + n, lift, side });
  };

  const wrapIfNeeded = (need) => {
    if (px + need > RIGHT) {
      row++;
      px = LEFT + p.random(-4, 4);
      return true;
    }
    return false;
  };

  // a cursive loop of height s; advances 2.2*s
  const loop = (s) => {
    const adv = 2.2 * s;
    const lifted = wrapIfNeeded(adv);
    const c = adv / p.TWO_PI;
    const steps = Math.max(18, Math.round(s * 3.2));
    for (let i = 0; i <= steps; i++) {
      const t = (i / steps) * p.TWO_PI;
      push(px + c * t - s * 0.9 * Math.sin(t), baseY() - s * (1 - Math.cos(t)),
        lifted && i === 0);
    }
    px += adv;
  };

  // a turning circle: the pen goes round once in place, then moves on
  const turn = (r) => {
    const adv = 2.6 * r;
    const lifted = wrapIfNeeded(adv);
    const cx = px + r, cy = baseY() - r;
    const steps = 44;
    for (let i = 0; i <= steps; i++) {
      const t = Math.PI / 2 + (i / steps) * p.TWO_PI; // start at the bottom
      push(cx + r * Math.cos(t), cy + r * Math.sin(t), lifted && i === 0);
    }
    // short connecting stroke along the baseline
    for (let i = 1; i <= 6; i++) push(cx + (adv - r) * (i / 6), baseY());
    px += adv;
  };

  const build = () => {
    pts = [];
    row = 0; px = LEFT; slant = 0; seed = 10.0; side = 0;
    const BIG = 13, SMALL = 6.5, R = 8;
    for (let i = 0; i < 41; i++) loop(BIG);
    // the writer's entry: only the slant changes
    side = 1; slant = p.radians(4); seed = 50.0;
    for (let i = 0; i < 6; i++) loop(BIG);
    // the size cue
    for (let i = 0; i < 64; i++) loop(SMALL);
    // seven circles; the writer returns after the fourth
    for (let i = 0; i < 7; i++) {
      if (i === 4) { side = 0; slant = 0; seed = 90.0; }
      turn(R);
    }
    while (row < 6) loop(BIG);
    // drop whatever spilled onto a seventh row
    pts = pts.filter((q) => q.y < TOP + 5.6 * ROW);
  };

  p.setup = () => {
    p.createCanvas(W, H);
    p.randomSeed(101);
    p.noiseSeed(101);
    build();
    startFrame = p.frameCount;
  };

  p.draw = () => {
    p.background(18, 18, 24);
    const age = p.frameCount - startFrame;
    shown = Math.min(pts.length, age * PER_FRAME);
    if (age * PER_FRAME > pts.length + HOLD * PER_FRAME) {
      startFrame = p.frameCount;
      shown = 0;
    }

    p.noFill();
    p.strokeWeight(1.4);
    p.strokeJoin(p.ROUND);
    p.stroke(232, 226, 212, 230);
    p.beginShape();
    for (let i = 0; i < shown; i++) {
      const q = pts[i];
      if (q.lift && i > 0) { p.endShape(); p.beginShape(); }
      p.vertex(q.x, q.y);
    }
    p.endShape();

    if (shown > 0 && shown < pts.length) {
      const q = pts[shown - 1];
      p.noStroke();
      p.fill(255, 236, 200, 220);
      p.circle(q.x, q.y, 4);
    }
  };
}