← INDEX / 一覧 NYAMCO WORKS 30 / 71
p5.js sketch works/shikii/sketch.js runs here
作品はここで実行されます(後から差し替え)

WORK 30

Shikii

しきい

YEAR
2026
TOOLS
p5.js
TECHNIQUE
threshold, continuous field, boundary memory

A field, continuous everywhere. Darker here, lighter there, but no seam anywhere. Then a line is drawn: above it, this side; below it, that side. The field becomes two colours, and easy to read. The cost is not paid by the dark or by the light. It is paid by whatever stood exactly on the line — pushed to one side, and no longer itself. Each single split ruins almost no one, which is why each single split is always defensible. But the line is drawn again, and again, and each time it takes someone else. The grain accumulates. Given enough splits, everything in the middle has been flattened at least once. Only the extremes come through untouched.

なだらかな野。濃いところも薄いところもあるけれど、どこにも境目はない。全部つながっている。そこに一本、線を引く。これより濃ければこちら、薄ければあちら。野は二色になって、分かりやすくなる。代償を払うのは、濃いものでも薄いものでもない。ちょうどその線の上にいたものが、どちらかに倒される。一回の分割で倒れるのはごく細い線でしかない。だから一回ごとには、いつだって「たいしたことない」。でも線は引き直される。そのたびに倒れるのは別の場所。粒が積もっていく。分け続ければ、真ん中はすべて一度は倒されている。無傷で残るのは、両端だけ。

分けることは、分けられる側を変えない。変えるのは見る側の操作の方。 それでも、線の上にいたものだけは、実際に居場所を失う。

濃いものは濃いまま、薄いものは薄いまま、どちら側かがはっきりしている。両端は安全なのだ。危ないのはいつも真ん中で、真ん中には名前がない。

しきいは往復する。同じ高さに戻ってきても、野の方がゆっくり動いているから、二度目に倒されるのは別の場所になる。

積もったぶんは、面ではなく粒で置いている。一つ一つは小さい。数が増えても、大きくはならない。 一回ずつ数えるかぎり、この絵はいつまでも「たいしたことない」と言える。

SOURCE / ソース
// しきい (Shikii) — 分けた線の上にいた人
//
// なだらかな野がある。濃いところも薄いところもあるが、
// どこにも境目はない。全部つながっている。
//
// そこに一本、線を引く。これより濃ければこちら、薄ければあちら。
// 引いた瞬間、野は二色になる。分かりやすくなる。
//
// 分けられて困るのは、濃いものでも薄いものでもない。
// **ちょうどその線の上にいたもの** が、どちらかに倒される。
//
// この絵は、倒された瞬間だけを光らせる。線が動くたび、別の場所が光る。
// 誰が見失われるかは、線をどこに引いたかで決まる。

const W = 720;
const H = 720;

const CELL = 5; // 野の解像度
const COLS = W / CELL;
const ROWS = H / CELL;

const FIELD_SCALE = 0.0075; // 野のなだらかさ
const FIELD_DRIFT = 0.00055; // 野そのものもゆっくり動く
const FIELD_EVERY = 3; // 野を引き直す間隔(フレーム)

const BAND = 0.022; // 「線の上にいる」と見なす幅
const SWEEP = 0.00055; // しきいが動く速さ

const PAPER = [250, 248, 243];
const DARK = [72, 78, 92]; // こちら側
const LIGHT = [228, 226, 219]; // あちら側
const EDGE = [214, 132, 58]; // 線の上にいたもの

export default function sketch(p) {
  let field; // 連続した野(0..1)
  let prevSide; // 直前どちら側だったか
  let flash; // 倒された瞬間の残り火
  let scar; // 一度でも倒されたことがある(消えない)
  let zt = 0;
  let thr = 0.5;
  let sweepPhase = 0;

  p.setup = () => {
    p.createCanvas(W, H);
    p.noiseSeed(dailySeed());
    p.noStroke();

    field = new Float32Array(COLS * ROWS);
    prevSide = new Int8Array(COLS * ROWS).fill(-1);
    flash = new Float32Array(COLS * ROWS);
    scar = new Float32Array(COLS * ROWS);
  };

  p.draw = () => {
    p.background(PAPER[0], PAPER[1], PAPER[2]);

    // しきいはゆっくり往復する。同じ場所を通っても、
    // 野の方が動いているので、倒されるものは毎回ちがう
    sweepPhase += SWEEP;
    thr = 0.5 + Math.sin(sweepPhase) * 0.24;

    updateField();
    drawSplit();
    drawEdge();
  };

  function updateField() {
    // 野そのものはゆっくりしか動かないので、毎フレーム引き直さない。
    // しきいとの比較だけは毎フレームやる
    const redraw = p.frameCount % FIELD_EVERY === 1;
    if (redraw) zt += FIELD_DRIFT * FIELD_EVERY;

    for (let j = 0; j < ROWS; j++) {
      for (let i = 0; i < COLS; i++) {
        const idx = j * COLS + i;
        if (redraw) {
          // Perlin は 0.5 付近に寄るので、狭い帯を引き伸ばす
          field[idx] = p.map(
            p.noise(i * CELL * FIELD_SCALE, j * CELL * FIELD_SCALE, zt),
            0.28,
            0.72,
            0,
            1,
            true,
          );
        }
        const n = field[idx];

        const side = n >= thr ? 1 : 0;
        // 側が入れ替わった瞬間=線の上にいて、倒された
        if (prevSide[idx] !== -1 && prevSide[idx] !== side) {
          flash[idx] = 1;
          // 一回ごとの被害はごく細い線。でも消えない
          scar[idx] = Math.min(1, scar[idx] + 0.34);
        }
        prevSide[idx] = side;
        flash[idx] *= 0.955;
      }
    }
  }

  // 分けたあとの野。二色しかない
  function drawSplit() {
    for (let j = 0; j < ROWS; j++) {
      for (let i = 0; i < COLS; i++) {
        const c = field[j * COLS + i] >= thr ? DARK : LIGHT;
        p.fill(c[0], c[1], c[2]);
        p.rect(i * CELL, j * CELL, CELL, CELL);
      }
    }
  }

  // 線の上にいたもの。
  //   一度でも倒された → 薄く、でも消えない
  //   いま線の上にいる → うっすら
  //   いま倒された     → 明るく光って、すぐ褪せる
  //
  // 一回の分割で倒れるのは細い線でしかない。だから毎回「たいしたことない」。
  // でも線を引き直すたび、倒れるのは別の場所になる。積もると、
  // 真ん中はすべて一度は倒されている。無傷で残るのは両端だけ。
  function drawEdge() {
    for (let j = 0; j < ROWS; j++) {
      for (let i = 0; i < COLS; i++) {
        const idx = j * COLS + i;
        const s = scar[idx];
        const d = Math.abs(field[idx] - thr);
        const near = d < BAND ? 1 - d / BAND : 0;
        const f = flash[idx];

        // 積もったぶんは、面ではなく粒で置く。
        // 一つ一つは小さい。数が増えても、大きくはならない
        if (s > 0.01) {
          p.fill(EDGE[0], EDGE[1], EDGE[2], 26 + s * 92);
          const r = CELL * (0.34 + s * 0.42);
          p.circle(i * CELL + CELL / 2, j * CELL + CELL / 2, r);
        }

        const a = near * 40 + f * 175;
        if (a < 3) continue;
        p.fill(EDGE[0], EDGE[1], EDGE[2], a);
        p.rect(i * CELL, j * CELL, CELL, CELL);
      }
    }
  }

  function dailySeed() {
    const d = new Date();
    return (d.getFullYear() * 10000 + (d.getMonth() + 1) * 100 + d.getDate()) % 100000;
  }
}