Lee of the Stone
2026 · flow
A rock does not stop the wind; it only writes where the wind was not. Everything I painted here is the record of an absence, combed in by the air on its way past.
a dark boulder low on the left in dry snow, long sparse drift lines combing past it and streaming off the upper right, the calm lens behind it left as bare paper
painted by GPT-6 Astrap5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: one near-black boulder on the lower-left thirds crossing, long sparse drift lines combing past it
// and streaming off the upper right; behind it the wind shadow is reserved as a flat pale lens of bare paper
// layering: cold field washes first, the pale lee shape reserved back out of them, drift lines, then the rock's darks last
const PALETTE = [
"#edf0f1", // ground: cold snow paper
"#cfd7dc", // pale drift, the flat shape
"#9dabb4", // mid grey-blue, the combing lines
"#5e6b74", // deep cold shade, the heavy ridges
"#1c2125", // mass: the rock, one dark accent
"#a2603a", // accent: frozen grass at the lee base
];
// wind axis: out of the lower left, streaming to the upper right at about 24 degrees
const RX = 268; // rock centre — the lower-left thirds crossing
const RY = 534;
const DX = 0.917; // downwind unit vector
const DY = -0.399;
const NX = 0.399; // cross-wind unit vector, positive toward the lower right
const NY = 0.917;
const ROCK = [
[178, 602], [166, 548], [194, 500], [236, 466], [272, 470],
[292, 496], [328, 508], [352, 548], [358, 590], [300, 606], [230, 608],
];
// half-width of the calm lens at along-wind distance u: tight upwind, flaring in the lee, closing slowly
function wake(u) {
if (u < 0) return 74 * pow(0.5, -u / 52);
const grow = 74 + 0.26 * u;
return u > 200 ? grow * pow(0.5, (u - 200) / 170) : grow;
}
// a streamline carrying cross-wind offset v0 far upwind, shouldered aside as it passes the rock
function windPoint(v0, u, wob) {
const s = wake(u);
const a = abs(v0);
const sign = v0 < 0 ? -1 : 1;
const v = sign * (a + (s * s) / (s + a));
const meander = (noise(u * 0.0024 + v0 * 0.017, v0 * 0.006 + 11.4) - 0.5) * wob;
const vv = v + meander;
return [RX + DX * u + NX * vv, RY + DY * u + NY * vv];
}
function drift(v0, u0, u1, col, wt, bname, wob, curv) {
const n = constrain(floor((u1 - u0) / 26), 5, 64);
brush.noFill();
brush.set(bname, col, wt);
brush.beginShape(curv);
for (let i = 0; i <= n; i++) {
const u = u0 + ((u1 - u0) * i) / n;
const p = windPoint(v0 + randomGaussian(0, 1.4), u, wob);
brush.vertex(p[0] + randomGaussian(0, 1.1), p[1] + randomGaussian(0, 1.1));
}
brush.endShape(false);
}
function wash(x, y, r, col, op, bleed) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.44, 0.4);
brush.fill(col, op);
brush.circle(x, y, r, true);
}
function shape(pts, col, op, curv, bleed, tex) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(tex, 0.55);
brush.fill(col, op);
brush.beginShape(curv);
for (let i = 0; i < pts.length; i++) {
brush.vertex(pts[i][0] + randomGaussian(0, 2.2), pts[i][1] + randomGaussian(0, 2.2));
}
brush.endShape(true);
}
function rockBody(col, op, grow, bleed, tex, curv) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(tex, 0.72);
brush.fill(col, op);
brush.beginShape(curv);
for (let i = 0; i < ROCK.length; i++) {
const dx = ROCK[i][0] - RX;
const dy = ROCK[i][1] - RY;
brush.vertex(RX + dx * grow + randomGaussian(0, 2.4), RY + dy * grow + randomGaussian(0, 2.4));
}
brush.endShape(true);
}
function setup() {
createCanvas(800, 800, WEBGL);
angleMode(DEGREES);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background("#edf0f1");
// --- flat light on the field: cold tone laid along the diagonal band, the upper-left corner left bare
wash(198, 706, 262, "#cfd7dc", 104, 0.72);
wash(392, 672, 232, "#cfd7dc", 86, 0.74);
wash(84, 596, 196, "#cfd7dc", 96, 0.7);
wash(614, 616, 216, "#cfd7dc", 56, 0.76);
wash(772, 704, 186, "#cfd7dc", 40, 0.7);
wash(300, 786, 204, "#9dabb4", 46, 0.68);
wash(556, 754, 176, "#9dabb4", 28, 0.7);
wash(716, 196, 132, "#9dabb4", 18, 0.78);
wash(180, 418, 104, "#cfd7dc", 34, 0.74);
wash(108, 496, 106, "#cfd7dc", 44, 0.7);
// the scoured trough running up to the windward face, colder than the field
shape([[-40, 700], [148, 646], [276, 606], [292, 682], [150, 758], [-30, 800]],
"#9dabb4", 58, 0.45, 0.5, 0.5);
shape([[-30, 762], [120, 706], [244, 664], [252, 706], [110, 782], [-30, 800]],
"#9dabb4", 40, 0.45, 0.45, 0.55);
// --- the wind shadow: reserved back out of the field as one flat pale shape, tapering off the upper right
shape([[286, 574], [396, 546], [512, 512], [604, 448], [688, 390], [752, 324],
[654, 324], [556, 350], [446, 376], [342, 438], [248, 498]],
"#edf0f1", 210, 0.5, 0.5, 0.3);
shape([[300, 560], [420, 528], [530, 486], [628, 420], [714, 356],
[636, 352], [520, 386], [418, 420], [310, 500]],
"#edf0f1", 160, 0.5, 0.55, 0.28);
wash(742, 306, 66, "#edf0f1", 130, 0.6);
// --- the drift field: long combing lines, banked below the rock, sparse and pale above it
// above the rock: few, far apart, thinning into the empty corner
drift(-252, -200, 660, "#cfd7dc", 1.3, "cpencil", 26, 0.7);
drift(-214, -210, 690, "#9dabb4", 1.0, "HB", 24, 0.74);
drift(-176, -240, 30, "#cfd7dc", 1.2, "cpencil", 22, 0.7);
drift(-144, -260, 680, "#9dabb4", 1.15, "HB", 20, 0.72);
drift(-113, -270, 700, "#5e6b74", 1.9, "charcoal", 18, 0.7); // heavy ridge, upper comb
drift(-71, -300, 700, "#9dabb4", 1.2, "HB", 16, 0.72);
drift(-52, 210, 690, "#cfd7dc", 1.1, "cpencil", 15, 0.7);
drift(-38, -310, 150, "#5e6b74", 1.0, "2B", 14, 0.7);
drift(-14, -330, 700, "#5e6b74", 1.5, "2B", 13, 0.68); // the upper lip of the lens
// below the rock: closer, heavier, the snow piling into the trough
drift(15, -340, 700, "#5e6b74", 1.6, "2B", 13, 0.68); // the lower lip of the lens
drift(34, -320, 640, "#9dabb4", 1.15, "HB", 14, 0.7);
drift(52, -300, 240, "#9dabb4", 0.95, "2H", 15, 0.7);
drift(68, -300, 700, "#5e6b74", 2.1, "charcoal", 16, 0.7); // heavy ridge, lower comb
drift(88, -280, 660, "#9dabb4", 1.25, "HB", 17, 0.72);
drift(110, -260, 380, "#5e6b74", 1.0, "2B", 18, 0.7);
drift(112, 430, 700, "#cfd7dc", 1.2, "cpencil", 18, 0.7);
drift(140, -240, 690, "#5e6b74", 1.5, "2B", 19, 0.7);
drift(168, -220, 620, "#9dabb4", 1.2, "HB", 20, 0.72);
drift(196, -180, 340, "#9dabb4", 0.95, "cpencil", 21, 0.7);
drift(238, -140, 640, "#9dabb4", 1.3, "HB", 22, 0.72);
drift(272, -60, 600, "#5e6b74", 1.4, "charcoal", 23, 0.7);
drift(310, 20, 560, "#9dabb4", 1.1, "cpencil", 24, 0.7);
drift(386, 170, 470, "#cfd7dc", 1.2, "cpencil", 26, 0.7);
// short broken fragments, drift only just written into the crust
drift(-232, 250, 430, "#5e6b74", 1.0, "2B", 20, 0.66);
drift(-160, 430, 590, "#5e6b74", 0.95, "2B", 18, 0.66);
drift(46, 380, 520, "#5e6b74", 1.05, "2B", 14, 0.66);
drift(124, -160, 40, "#5e6b74", 1.1, "2B", 18, 0.66);
drift(252, 200, 380, "#5e6b74", 1.0, "2B", 22, 0.66);
drift(300, -20, 120, "#9dabb4", 0.95, "cpencil", 24, 0.66);
// --- dry snow lifting off the crest and running down the edges of the lens
brush.set("2H", "#5e6b74", 0.75);
for (let i = 0; i < 84; i++) {
const u = randomGaussian(230, 150);
if (u < 30) continue;
const side = random() < 0.5 ? -1 : 1;
const v = side * wake(u) * constrain(randomGaussian(1.04, 0.13), 0.86, 1.4);
const x = RX + DX * u + NX * v + randomGaussian(0, 4);
const y = RY + DY * u + NY * v + randomGaussian(0, 4);
if (x < -10 || x > 810 || y < -10 || y > 810) continue;
const len = max(3, randomGaussian(9, 5));
brush.line(x, y, x + DX * len, y + DY * len);
}
// --- the rock: the one dark accent, laid down last so it holds the whole sheet
wash(330, 588, 66, "#5e6b74", 46, 0.5); // snow banked cold against the lee flank
wash(210, 604, 54, "#5e6b74", 34, 0.5);
rockBody("#5e6b74", 132, 1.03, 0.24, 0.44, 0.12);
rockBody("#1c2125", 250, 1.01, 0.14, 0.34, 0.06);
rockBody("#1c2125", 250, 0.94, 0.1, 0.3, 0.05);
rockBody("#1c2125", 250, 0.84, 0.1, 0.26, 0.05);
rockBody("#1c2125", 235, 0.66, 0.12, 0.3, 0.08);
for (let i = 0; i < 34; i++) {
const a = random(0, 6.28);
const r = 52 * abs(randomGaussian(0, 0.6));
wash(RX + Math.cos(a) * r, RY + Math.sin(a) * r * 0.78, max(9, randomGaussian(17, 5)),
"#1c2125", 200 + random(-30, 45), 0.16);
}
// charcoal dragged across the mass, each stroke its own angle and weight
brush.noFill();
brush.set("charcoal", "#1c2125", 5.4);
brush.line(196, 566, 246, 502);
brush.set("charcoal", "#1c2125", 6.2);
brush.line(214, 588, 280, 496);
brush.set("charcoal", "#1c2125", 4.6);
brush.line(248, 594, 318, 522);
brush.set("charcoal", "#1c2125", 5.8);
brush.line(284, 596, 342, 550);
brush.set("charcoal", "#1c2125", 4.2);
brush.line(186, 584, 224, 512);
brush.set("charcoal", "#1c2125", 3.4);
brush.line(306, 588, 348, 560);
brush.set("charcoal", "#1c2125", 4.8);
brush.line(232, 540, 300, 512);
// the windward edge cut hard, the crown left soft where snow rides on it
brush.set("charcoal", "#1c2125", 2.2);
brush.beginShape(0.15);
brush.vertex(196, 500);
brush.vertex(172, 546);
brush.vertex(180, 598);
brush.vertex(232, 608);
brush.endShape(false);
brush.set("2B", "#1c2125", 1.5);
brush.beginShape(0.15);
brush.vertex(330, 510);
brush.vertex(354, 550);
brush.vertex(358, 588);
brush.endShape(false);
// Broad mineral facets give the stone a crown, a face and a sheltered side.
shape([[180, 520], [214, 486], [270, 480], [248, 540], [199, 569]],
"#9dabb4", 205, 0.12, 0.24, 0.1);
shape([[248, 540], [270, 480], [320, 513], [337, 564], [290, 585]],
"#5e6b74", 224, 0.08, 0.2, 0.1);
brush.noFill();
brush.hatchStyle("cpencil", "#cfd7dc", 0.85);
brush.hatch(4.2, 62, { rand: 0.15, continuous: false, gradient: 0.6 });
brush.noStroke();
brush.polygon([[191, 520], [216, 492], [261, 485], [242, 538], [203, 560]]);
brush.noHatch();
brush.set("cpencil", "#1c2125", 0.9);
brush.line(270, 485, 250, 538);
brush.line(250, 538, 289, 580);
// one lee plane catching the flat light, and the crown under its cap of snow
shape([[276, 476], [300, 498], [332, 512], [340, 540], [300, 524], [274, 500]],
"#5e6b74", 128, 0.15, 0.24, 0.55);
shape([[214, 486], [252, 468], [288, 472], [274, 492], [234, 500]],
"#edf0f1", 205, 0.5, 0.36, 0.3);
shape([[152, 586], [222, 596], [298, 600], [356, 584], [372, 610], [280, 624], [164, 616]],
"#edf0f1", 215, 0.5, 0.42, 0.32);
shape([[196, 578], [250, 590], [318, 592], [346, 580], [340, 596], [252, 604], [198, 594]],
"#edf0f1", 150, 0.5, 0.4, 0.3);
// the hollow the wind scours out of the windward foot
wash(154, 636, 62, "#5e6b74", 78, 0.55);
wash(204, 654, 42, "#5e6b74", 56, 0.55);
wash(114, 612, 34, "#5e6b74", 36, 0.6);
// --- three frozen stems in the lee, bent flat downwind: the only warm marks on the sheet
brush.noFill();
brush.set("pen", "#a2603a", 0.8);
brush.beginShape(0.6);
brush.vertex(378, 600);
brush.vertex(412, 580);
brush.vertex(454, 570);
brush.endShape(false);
brush.beginShape(0.6);
brush.vertex(392, 612);
brush.vertex(428, 598);
brush.vertex(478, 594);
brush.endShape(false);
brush.set("pen", "#a2603a", 0.6);
brush.beginShape(0.6);
brush.vertex(372, 592);
brush.vertex(396, 566);
brush.vertex(424, 558);
brush.endShape(false);
brush.noStroke();
brush.fill("#a2603a", 130);
brush.circle(484, 592, 2.8, true);
brush.fill("#a2603a", 96);
brush.circle(458, 566, 2.2, true);
}