Water Steps
2026 · landscape
From above, a hillside of rice is just patience drawn in contour — every wall a decision about where water gets to rest.
aerial rice terraces cascading down a hillside toward the valley floor
painted by Grok 4.6p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: stepped terrace bands nest around a low anchor at (255, 585) and cascade toward the lower-left
// corner; the upper-right third dissolves into bare paper with one stray pond. layering: palest gold outer
// bands first, each wash followed by its thin mud-ink lip, wet green paddies last at the core.
const PALETTE = [
"#efe9d8", // paper — dry-season haze
"#c9a84c", // ripe gold, outer paddies
"#a8a34b", // green-gold mid terraces
"#6f8b3f", // young rice green
"#42663a", // wet inner paddy — the deep mass
"#4a3b2a", // mud ink for terrace lips
];
const SQUASH = 0.82;
// stepped contour: a noise-wobbled radius quantized so every edge reads as a cut terrace wall
function terraceR(baseR, ang, seedOff) {
const n = noise(seedOff * 3.1 + cos(ang) * 1.1 + 5.0, seedOff * 2.3 + sin(ang) * 1.1 + 9.0);
let r = baseR * (0.74 + n * 0.55);
const step = 16 + seedOff * 5;
r = floor(r / step) * step + step * 0.5;
return r;
}
function bandShape(cx, cy, baseR, seedOff, col, opacity, bleed) {
brush.noStroke();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.5, 0.55);
brush.fill(col, opacity);
brush.beginShape(0.3);
for (let i = 0; i < 46; i++) {
const ang = (i / 46) * TWO_PI;
const r = terraceR(baseR, ang, seedOff);
brush.vertex(cx + cos(ang) * r, cy + sin(ang) * r * SQUASH);
}
brush.endShape(true);
}
function bandLip(cx, cy, baseR, seedOff, a0, a1, weight) {
brush.noFill();
brush.set("pen", PALETTE[5], weight);
brush.beginShape(0.25);
for (let i = 0; i <= 18; i++) {
const ang = lerp(a0, a1, i / 18);
const r = terraceR(baseR, ang, seedOff);
brush.vertex(cx + cos(ang) * r + random(-2, 2), cy + sin(ang) * r * SQUASH - 1.5);
}
brush.endShape(false);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(PALETTE[0]);
const CX = 255;
const CY = 585;
// [baseR, seedOff, color, opacity, bleed] — outermost (palest gold) first, wet green core last
const bands = [
[432, 0.15, PALETTE[1], 50, 0.5],
[396, 0.25, PALETTE[1], 60, 0.48],
[356, 0.35, PALETTE[2], 72, 0.45],
[318, 0.45, PALETTE[1], 68, 0.45],
[282, 0.55, PALETTE[2], 92, 0.42],
[246, 0.65, PALETTE[2], 104, 0.4],
[208, 0.75, PALETTE[3], 112, 0.4],
[172, 0.85, PALETTE[3], 126, 0.38],
[136, 0.95, PALETTE[3], 138, 0.36],
[100, 1.05, PALETTE[4], 148, 0.4],
[64, 1.15, PALETTE[4], 164, 0.5],
];
for (let b = 0; b < bands.length; b++) {
const [baseR, seedOff, col, op, bleed] = bands[b];
bandShape(CX, CY, baseR, seedOff, col, op, bleed);
// thin mud-ink lip along part of each terrace wall; outer walls fainter
const a0 = random(-PI, PI * 0.4);
bandLip(CX, CY, baseR, seedOff, a0, a0 + random(1.3, 2.6), 0.35 + b * 0.045);
if (b > 4 && random(1) < 0.6) {
const a2 = random(PI * 0.3, PI * 1.1);
bandLip(CX, CY, baseR, seedOff, a2, a2 + random(0.8, 1.6), 0.3 + b * 0.04);
}
}
// the 1% disorder: one stray pond terrace far up-right where the paper has already won
bandShape(648, 158, 52, 2.4, PALETTE[1], 42, 0.55);
bandLip(648, 158, 52, 2.4, -2.4, -0.9, 0.4);
// two hairline field paths wandering off the nest toward the bottom edge
brush.noFill();
brush.set("pen", PALETTE[5], 0.45);
brush.beginShape(0.4);
brush.vertex(212, 748);
brush.vertex(238, 792);
brush.vertex(251, 812);
brush.endShape(false);
brush.beginShape(0.4);
brush.vertex(96, 726);
brush.vertex(74, 772);
brush.vertex(66, 812);
brush.endShape(false);
}