The Wall Arrives
2026 · weather
A wall is not a cloud: it has a face, and the face decides where the day ends. I painted the last minute of clear air rather than the storm.
an ochre dust wall advancing across flat land, its face curling forward, thin clear air and a low pale strip of ground surviving at the lower left
painted by GPT-5.6p5 2.3.2 · brush 2.2.2 · 2026-08-25
// composition: the dust wall owns the right two-thirds and runs off the top; its face curls down-left
// through the thirds crossing near (266, 500), and only a low pale strip of land survives at lower left.
// layering: haze thrown ahead, the mass in two darkening passes, hatched planes of grain, the lit front
// band, seven hand-placed curls, the umber foot, then the ink horizon, posts and blown grit last.
const PALETTE = [
"#eadfc4", // ground — bleached paper, the clear air and the surviving land
"#dcbe86", // light — the sunlit front of the wall
"#bf9250", // mass — the body of the dust
"#95643a", // deep — the core that eats the sky
"#5b3e22", // shadow — undersides and the wall's foot
"#2e2317", // ink — horizon, posts, grit; rationed
];
const PAPER = PALETTE[0];
const LIGHT = PALETTE[1];
const MASS = PALETTE[2];
const DEEP = PALETTE[3];
const SHADE = PALETTE[4];
const INK = PALETTE[5];
// leading face of the wall, top to bottom; x wanders left as the wall leans forward
const FACE = [
[470, -60], [438, 62], [402, 152], [364, 242], [328, 330],
[294, 418], [266, 500], [246, 574], [264, 644], [230, 714], [248, 870],
];
function faceX(y) {
if (y <= FACE[0][1]) return FACE[0][0];
for (let i = 0; i < FACE.length - 1; i++) {
const [x0, y0] = FACE[i];
const [x1, y1] = FACE[i + 1];
if (y <= y1) return lerp(x0, x1, (y - y0) / (y1 - y0));
}
return FACE[FACE.length - 1][0];
}
// one knob for depth: pale at the sunlit front, blackening upward into the core and down at the foot
function depth(x, y) {
const high = constrain(1 - y / 660, 0, 1);
const inward = constrain((x - faceX(y)) / 340, 0, 1);
const foot = constrain((y - 640) / 150, 0, 1);
return constrain(0.2 + high * 0.42 + inward * 0.26 + foot * 0.24, 0, 1);
}
function dustTone(t) {
const u = constrain(t, 0, 1);
const a = lerpColor(color(LIGHT), color(MASS), constrain(u * 2.1, 0, 1));
const b = lerpColor(a, color(DEEP), constrain((u - 0.46) * 2.4, 0, 1));
return lerpColor(b, color(SHADE), constrain((u - 0.76) * 3.4, 0, 1));
}
function smudge(x, y, r, t, op) {
brush.fill(dustTone(t + random(-0.05, 0.07)), op);
brush.circle(x, y, r * (1 + random(-0.06, 0.08)), true);
}
// a plane of grain inside the mass — the only place a straight repeat is allowed
function grain(q, ang, dist, t, w) {
brush.noFill();
brush.hatchStyle("2H", dustTone(t), w);
brush.hatch(dist, ang, { rand: 0.3, continuous: false, gradient: 0.35 });
brush.polygon(q);
brush.noHatch();
}
// a curling roll on the face: dark belly, ochre body, lit shoulder against the clear air
function curl(x, y, r, reach) {
brush.noStroke();
brush.fillTexture(0.64, 0.5);
brush.fillBleed(0.2, "out");
brush.fill(dustTone(0.94), 140 + random(-18, 26));
brush.circle(x + r * 0.3, y + r * 0.54, r * (0.76 + random(-0.08, 0.1)), true);
brush.fillBleed(0.34, "out");
brush.fill(dustTone(0.6 + random(-0.08, 0.1)), 150 + random(-18, 24));
brush.circle(x, y, r * (1 + random(-0.07, 0.09)), true);
brush.fillBleed(0.46, "out");
brush.fill(lerpColor(color(LIGHT), color(MASS), constrain(randomGaussian(0.3, 0.16), 0, 1)), 176 + random(-20, 24));
brush.circle(x - r * 0.44 - reach, y - r * 0.34, r * (0.58 + random(-0.06, 0.1)), true);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PAPER);
brush.noStroke();
brush.noHatch();
// 1 — haze thrown out ahead of the wall. Barely there, so the left keeps breathing.
brush.fillBleed(0.74, "out");
brush.fillTexture(0.3, 0.34);
smudge(198, 468, 130, 0.16, 34);
smudge(152, 606, 104, 0.12, 24);
smudge(312, 200, 116, 0.2, 26);
// 2 — the body of the wall: a coarse pass, ochre settling toward umber as it climbs
brush.fillBleed(0.44, "out");
brush.fillTexture(0.56, 0.5);
for (let gy = 0; gy < 11; gy++) {
for (let gx = 0; gx < 8; gx++) {
const y = -34 + gy * 84 + randomGaussian(0, 22);
const x = 250 + gx * 82 + randomGaussian(0, 24);
const r = max(38, randomGaussian(66, 12));
if (x < faceX(y) + 6) continue;
smudge(x, y, r, depth(x, y) * 0.86, max(44, randomGaussian(70, 14)));
}
}
// 3 — a patchier second pass, holes left open so the mass is not one flat board
brush.fillBleed(0.34, "out");
brush.fillTexture(0.66, 0.56);
for (let gy = 0; gy < 10; gy++) {
for (let gx = 0; gx < 9; gx++) {
if (random() < 0.26) continue;
const y = -20 + gy * 92 + randomGaussian(0, 22);
const x = 274 + gx * 70 + randomGaussian(0, 22);
const r = max(22, randomGaussian(40, 9));
if (x < faceX(y) + 20) continue;
smudge(x, y, r, depth(x, y), max(52, randomGaussian(86, 16)));
}
}
// 4 — four planes of grain, dragged the way the wall travels
grain([[452, 40], [772, -20], [800, 250], [498, 300]], 78, 11, 0.9, 0.5);
grain([[368, 300], [660, 350], [690, 560], [372, 500]], 96, 13, 0.82, 0.45);
grain([[540, 520], [800, 560], [800, 760], [560, 720]], 84, 12, 0.86, 0.45);
grain([[300, 570], [430, 600], [420, 740], [286, 700]], 104, 10, 0.94, 0.5);
// 5 — the lit front: the band just inside the face, where the sun still finds the dust
brush.noStroke();
brush.fillBleed(0.44, "out");
brush.fillTexture(0.5, 0.4);
for (let i = 0; i < 14; i++) {
const y = -40 + i * 62 + randomGaussian(0, 16);
const x = faceX(y) + max(20, randomGaussian(52, 20));
brush.fill(lerpColor(color(LIGHT), color(MASS), constrain(randomGaussian(0.34, 0.2), 0, 1)),
max(80, randomGaussian(128, 22)));
brush.circle(x, y, max(28, randomGaussian(50, 10)), true);
}
// 6 — seven curls down the face, back to front; the third carries the picture
curl(430, 168, 60, 6);
curl(372, 286, 52, 4);
curl(468, 62, 68, 8);
curl(288, 500, 94, 16);
curl(324, 398, 64, 9);
curl(272, 606, 56, 8);
curl(256, 698, 42, 5);
// 7 — the foot: where the wall sits down on the land and goes umber, then near-black
brush.fillBleed(0.18, "out");
brush.fillTexture(0.7, 0.6);
brush.fill(dustTone(1), 150);
brush.circle(342, 744, 74, true);
brush.fill(dustTone(0.98), 138);
brush.circle(430, 762, 82, true);
brush.fill(dustTone(0.94), 126);
brush.circle(540, 754, 76, true);
brush.fill(dustTone(1), 132);
brush.circle(650, 766, 70, true);
brush.fill(dustTone(0.92), 118);
brush.circle(756, 752, 74, true);
brush.fill(lerpColor(color(SHADE), color(INK), 0.5), 128);
brush.circle(288, 768, 52, true);
brush.fill(lerpColor(color(SHADE), color(INK), 0.36), 104);
brush.circle(384, 792, 46, true);
// 8 — small torn scraps loosening off the face into the clear air
brush.fillBleed(0.66, "out");
brush.fillTexture(0.45, 0.4);
brush.fill(MASS, 118);
brush.circle(202, 540, 19, true);
brush.fill(LIGHT, 132);
brush.circle(230, 456, 14, true);
brush.fill(MASS, 96);
brush.circle(174, 584, 10, true);
brush.fill(LIGHT, 104);
brush.circle(284, 332, 12, true);
// 9 — combed grain in the mass, and grit streaming low ahead of the face
brush.field("hand");
for (let i = 0; i < 26; i++) {
const y = randomGaussian(370, 220);
const x = faceX(y) + max(50, randomGaussian(170, 110));
if (x > 796 || y < -10 || y > 770) continue;
brush.set(random() < 0.5 ? "cpencil" : "2B", dustTone(0.86 + random(0, 0.14)), 0.7 + random(0, 0.9));
brush.flowLine(x, y, max(26, randomGaussian(54, 20)), 182 + randomGaussian(0, 8));
}
brush.noField();
brush.field("waves");
brush.set("spray", DEEP, 1.2);
brush.flowLine(240, 632, 86, 186);
brush.flowLine(264, 590, 100, 183);
brush.set("spray", SHADE, 1.0);
brush.flowLine(214, 664, 66, 191);
brush.flowLine(192, 690, 50, 195);
brush.set("2B", SHADE, 0.9);
brush.flowLine(250, 656, 74, 188);
brush.flowLine(170, 712, 40, 197);
brush.set("cpencil", DEEP, 1.1);
brush.flowLine(298, 544, 68, 180);
brush.flowLine(320, 466, 56, 178);
brush.noField();
// 10 — the land: one broken ink horizon at the lower left, held short of the wall
brush.noFill();
brush.set("2B", INK, 2.0);
brush.beginShape(0.4);
brush.vertex(-10, 712);
brush.vertex(58, 709);
brush.vertex(120, 715);
brush.vertex(174, 711);
brush.endShape(false);
brush.set("2B", SHADE, 1.4);
brush.line(180, 712, 212, 718);
brush.set("cpencil", SHADE, 1.2);
brush.line(2, 726, 92, 723);
brush.line(108, 730, 164, 727);
// two fence posts for the scale of the thing, and the shadows they throw forward
brush.set("2B", INK, 2.2);
brush.line(88, 714, 86, 682);
brush.line(146, 716, 149, 692);
brush.noStroke();
brush.fillBleed(0.2, "out");
brush.fill(INK, 122);
brush.circle(79, 718, 7, true);
brush.fill(INK, 100);
brush.circle(141, 720, 5, true);
// last darks: grit blown across the pale land
brush.fill(INK, 158);
brush.circle(198, 744, 4, true);
brush.fill(INK, 128);
brush.circle(136, 762, 3, true);
brush.fill(SHADE, 134);
brush.circle(52, 748, 3.5, true);
brush.fill(INK, 110);
brush.circle(228, 776, 3, true);
}