Chambered Interval
2026 · study
The animal never decided on this shape; it only kept adding a room the same shape as the last, and the curve is what arithmetic looks like from outside.
a nautilus cut in section on the lower-left third, chambers stepping outward, septa curving, the rest bare paper
painted by DeepSeek V4p5 2.3.2 · brush 2.2.2 · 2026-08-24
// composition: a nautilus cut in section, its pole on the lower-left thirds crossing and its
// mouth pushing off the bottom-left corner; the whorls shrink toward the middle of the sheet
// and the whole upper-right is left bare — a growth diagram, not a specimen plate.
// layering: ochre air first; chamber washes apex-outward, weighted by a light from upper-left;
// then inner-wall shadow, the curving septa, the wall, and pooled darks last.
const PALETTE = [
"#ece4d5", // ground — warm plate paper
"#c9a878", // mass — ochre chamber wash
"#8a6f4c", // mid — sepia shading and inner septa
"#332a20", // shadow — dark sepia ink
"#5f8580", // accent — nacre, rationed to three chambers
];
const GROUND = PALETTE[0];
const OCHRE = PALETTE[1];
const SEPIA = PALETTE[2];
const INK = PALETTE[3];
const NACRE = PALETTE[4];
// the whole shell is one sentence: three times wider every turn, forever.
const PX = 252; // the pole
const PY = 498;
const R_AP = 392; // radius at the mouth
const TH_AP = 2.36; // mouth bearing — down and to the left
const STEP = (2 * Math.PI) / 11; // one chamber's share of a turn
const IN = 0.372; // inner wall: the back of the previous whorl
const OUT = 0.95;
const DARK_AT = 2.12; // the light comes from up-right; the mouth keeps the shadow
function radAt(th) {
return R_AP * pow(3, (th - TH_AP) / (2 * Math.PI));
}
function shellPt(th, frac, jig) {
const r = radAt(th) * frac + (jig ? randomGaussian(0, jig) : 0);
return [PX + Math.cos(th) * r, PY + Math.sin(th) * r];
}
function shade(th) {
return 0.5 + 0.5 * Math.cos(th - DARK_AT);
}
// the band of paper between two septa, walked out along the wall and back along the last whorl
function bandPoints(thA, thB, fLo, fHi, jig) {
const pts = [];
for (let i = 0; i <= 6; i++) {
const th = thA + (thB - thA) * (i / 6);
pts.push(shellPt(th, fHi + random(-0.012, 0.018), jig));
}
for (let i = 6; i >= 0; i--) {
const th = thA + (thB - thA) * (i / 6);
pts.push(shellPt(th, fLo + random(-0.014, 0.012), jig));
}
return pts;
}
function wash(thA, thB, fLo, fHi, col, alpha, jig, bleed) {
brush.noStroke();
brush.noHatch();
brush.fillBleed(bleed, "out");
brush.fillTexture(0.5 + random(0, 0.24), 0.6);
brush.fill(col, alpha);
const pts = bandPoints(thA, thB, fLo, fHi, jig);
brush.beginShape(0.55);
for (let i = 0; i < pts.length; i++) brush.vertex(pts[i][0], pts[i][1]);
brush.endShape(true);
}
// tone inside a chamber, brushed along the turn rather than ruled across it
function chamberGrain(thA, thB, n, bname, hex, weight, len) {
brush.noFill();
brush.set(bname, hex, weight);
for (let i = 0; i < n; i++) {
const th = thA + (thB - thA) * random();
const f = constrain(randomGaussian(0.63, 0.15), IN + 0.05, OUT - 0.05);
const p = shellPt(th, f, 1.2);
const deg = (Math.atan2(Math.cos(th), -Math.sin(th)) * 180) / Math.PI;
brush.flowLine(p[0], p[1], max(4, randomGaussian(len, len * 0.32)), deg + randomGaussian(0, 15));
}
}
// a septum: concave to the mouth, bulging back toward the apex it grew away from
function septum(th, bname, hex, weight, bulgeK) {
const r = radAt(th);
const bx = Math.sin(th);
const by = -Math.cos(th);
brush.noHatch();
brush.noFill();
brush.set(bname, hex, weight);
brush.beginShape(0.6);
for (let i = 0; i <= 5; i++) {
const t = i / 5;
const rr = r * (IN - 0.035 + (OUT + 0.065 - IN + 0.035) * t);
const off = r * bulgeK * Math.sin(Math.PI * t) + randomGaussian(0, 0.9);
brush.vertex(PX + Math.cos(th) * rr + bx * off, PY + Math.sin(th) * rr + by * off);
}
brush.endShape(false);
}
function spiralStroke(thStart, thEnd, frac, bname, hex, weight) {
const n = min(240, max(10, floor(abs(thEnd - thStart) * 9)));
brush.noHatch();
brush.noFill();
brush.set(bname, hex, weight);
brush.beginShape(0.5);
for (let i = 0; i <= n; i++) {
const th = thStart + (thEnd - thStart) * (i / n);
const p = shellPt(th, frac, radAt(th) * 0.01 + 0.4);
brush.vertex(p[0], p[1]);
}
brush.endShape(false);
}
function pool(th, frac, r, alpha, hex) {
const p = shellPt(th, frac, 1.8);
brush.noStroke();
brush.noHatch();
brush.fillBleed(0.36, "out");
brush.fillTexture(0.62, 0.6);
brush.fill(hex, alpha);
brush.circle(p[0], p[1], r, true);
}
function setup() {
createCanvas(800, 800, WEBGL);
brush.scaleBrushes(2.4);
noLoop();
}
function draw() {
translate(-width / 2, -height / 2);
background(GROUND);
// --- the air the shell sits in: four wide ochre breaths, lower-left only
brush.noStroke();
brush.fillBleed(0.68, "out");
brush.fillTexture(0.34, 0.36);
brush.fill(OCHRE, 22);
brush.circle(196, 578, 276, true);
brush.fill(OCHRE, 15);
brush.circle(352, 444, 202, true);
brush.fill(SEPIA, 16);
brush.circle(126, 714, 220, true);
brush.fill(NACRE, 10);
brush.circle(318, 616, 156, true);
// --- where the septa fall: eleven to a turn, every interval nudged by hand
const SEPTA = [];
let th = TH_AP - 1.36 * STEP;
for (let k = 0; k < 34; k++) {
SEPTA.push(th);
th -= STEP * (1 + randomGaussian(0, 0.05));
}
// --- chamber washes, apex outward, so the newest paint is the widest chamber
for (let j = 33; j >= 5; j--) {
const a = SEPTA[j - 1];
const b = SEPTA[j];
const mid = (a + b) / 2;
const r = radAt(b);
const sh = shade(mid);
const tight = constrain(1 - r / R_AP, 0, 1);
const t = constrain(0.1 + 1.25 * sh + randomGaussian(0, 0.1), 0, 1.9);
const col = t < 1
? lerpColor(color(OCHRE), color(SEPIA), t)
: lerpColor(color(SEPIA), color(INK), constrain(t - 1, 0, 0.9));
const al = constrain(randomGaussian(72 + 118 * sh + 22 * tight, 16), 52, 214);
wash(a, b, IN, OUT, col, al, r * 0.018, 0.11 + random(0, 0.08));
if (sh > 0.58 && r > 24) {
wash(a, b, IN, IN + 0.3, INK, constrain(randomGaussian(88, 16), 40, 134), r * 0.026, 0.2);
}
if (sh < 0.3 && r > 34 && random() < 0.6) {
wash(a, b, OUT - 0.26, OUT, GROUND, 86 + random(-18, 30), r * 0.026, 0.22);
}
}
// --- the five chambers you can actually read, each mixed on its own
wash(SEPTA[3], SEPTA[4], IN, OUT, lerpColor(color(SEPIA), color(INK), 0.26), 182, 3.2, 0.14);
wash(SEPTA[2], SEPTA[3], IN, OUT, lerpColor(color(SEPIA), color(INK), 0.38), 192, 3.6, 0.12);
wash(SEPTA[2], SEPTA[3], IN, IN + 0.28, INK, 104, 4.4, 0.2);
wash(SEPTA[1], SEPTA[2], IN, OUT, lerpColor(color(SEPIA), color(INK), 0.5), 200, 4.0, 0.15);
wash(SEPTA[0], SEPTA[1], IN, OUT, lerpColor(color(SEPIA), color(INK), 0.54), 196, 4.6, 0.12);
wash(TH_AP, SEPTA[0], IN, OUT, lerpColor(color(SEPIA), color(INK), 0.44), 184, 5.4, 0.11);
wash(TH_AP - 0.05, SEPTA[0] + 0.16, IN, IN + 0.34, INK, 112, 6.0, 0.22);
// two long sweeps that tie the outer rooms into one weight
wash(TH_AP, SEPTA[4], IN + 0.05, OUT - 0.05, INK, 34, 5.0, 0.26);
wash(SEPTA[1], SEPTA[7], IN, IN + 0.36, SEPIA, 46, 3.4, 0.28);
// nacre in three chambers only, cool against all that ochre
wash(SEPTA[8], SEPTA[9], IN + 0.1, OUT - 0.1, NACRE, 78, 2.4, 0.24);
wash(SEPTA[11], SEPTA[12], IN + 0.14, OUT - 0.16, NACRE, 66, 1.8, 0.26);
wash(SEPTA[6], SEPTA[7], IN + 0.08, 0.62, NACRE, 62, 2.8, 0.24);
// --- tone brushed along the whorl, densest in the rooms nearest the mouth
brush.field("curved");
chamberGrain(TH_AP, SEPTA[0], 26, "cpencil", INK, 0.55, 15);
chamberGrain(SEPTA[0], SEPTA[1], 22, "cpencil", INK, 0.5, 13);
chamberGrain(SEPTA[1], SEPTA[2], 20, "2B", INK, 0.45, 12);
chamberGrain(SEPTA[2], SEPTA[3], 16, "cpencil", SEPIA, 0.5, 11);
chamberGrain(SEPTA[3], SEPTA[4], 13, "cpencil", SEPIA, 0.45, 9);
chamberGrain(SEPTA[5], SEPTA[6], 9, "2H", SEPIA, 0.4, 7);
chamberGrain(SEPTA[7], SEPTA[8], 7, "2H", SEPIA, 0.35, 6);
brush.noField();
// --- the septa: pale and thin at the apex, drawn with a harder hand outward
for (let j = 33; j >= 6; j--) {
const r = radAt(SEPTA[j]);
if (r < 11) continue;
const soft = r < 44;
septum(
SEPTA[j],
soft ? "2H" : random() < 0.5 ? "cpencil" : "HB",
soft ? SEPIA : random() < 0.44 ? INK : SEPIA,
constrain(randomGaussian(soft ? 0.55 : 0.95, 0.15), 0.34, 1.3),
0.145 + random(-0.02, 0.035)
);
}
septum(SEPTA[5], "HB", SEPIA, 1.05, 0.152);
septum(SEPTA[4], "HB", INK, 1.15, 0.146);
septum(SEPTA[3], "2B", INK, 1.25, 0.158);
septum(SEPTA[2], "2B", INK, 1.3, 0.15);
septum(SEPTA[1], "2B", INK, 1.4, 0.162);
septum(SEPTA[1] + 0.03, "cpencil", SEPIA, 0.7, 0.172);
septum(SEPTA[0], "charcoal", INK, 1.45, 0.155);
septum(SEPTA[0] - 0.025, "2B", INK, 0.8, 0.168);
// --- the wall: one soft pass over every turn, then twice more where it counts
spiralStroke(TH_AP - 3.62 * 2 * Math.PI, TH_AP, 1.0, "cpencil", SEPIA, 1.0);
spiralStroke(TH_AP - 3.62 * 2 * Math.PI, TH_AP, 0.952, "2H", SEPIA, 0.5);
spiralStroke(TH_AP - 1.58 * 2 * Math.PI, TH_AP, 1.0, "2B", INK, 0.8);
// the wall is heaviest where the shell turns its back on the light, and lets go before the top
spiralStroke(1.34, 2.37, 1.0, "charcoal", INK, 1.7);
spiralStroke(0.52, 1.4, 1.0, "charcoal", INK, 1.35);
spiralStroke(-0.18, 0.58, 1.0, "2B", INK, 0.95);
spiralStroke(-1.02, -0.12, 1.0, "2B", SEPIA, 0.7);
spiralStroke(-1.86, -0.98, 1.0, "cpencil", SEPIA, 0.5);
spiralStroke(2.0, 2.36, 1.0, "charcoal", INK, 1.15);
// the wall combed with growth lines, five short passes, none the same length
spiralStroke(0.42, 1.62, 0.978, "2H", SEPIA, 0.4);
spiralStroke(1.05, 2.3, 0.965, "2H", INK, 0.36);
spiralStroke(-0.62, 0.34, 0.984, "2H", SEPIA, 0.34);
spiralStroke(1.72, 2.34, 0.99, "rotring", INK, 0.4);
spiralStroke(-1.5, -0.72, 0.972, "2H", SEPIA, 0.3);
// --- the mouth, cut across the last chamber, its lip left slightly open
brush.noHatch();
brush.noFill();
brush.set("charcoal", INK, 1.6);
const m0 = shellPt(TH_AP, IN - 0.02, 1.2);
const m1 = shellPt(TH_AP - 0.1, 0.68, 1.2);
const m2 = shellPt(TH_AP - 0.015, 1.008, 1.2);
brush.beginShape(0.5);
brush.vertex(m0[0], m0[1]);
brush.vertex(m1[0], m1[1]);
brush.vertex(m2[0], m2[1]);
brush.endShape(false);
brush.set("2B", INK, 0.7);
brush.line(m0[0] - 7, m0[1] - 8, m1[0] - 4, m1[1] - 12);
// --- the siphuncle: one thread through every room it ever built, broken where it dries out
spiralStroke(TH_AP - 1.45 * 2 * Math.PI, SEPTA[0], 0.55, "2H", SEPIA, 0.45);
brush.noStroke();
brush.fill(INK, 168);
for (let j = 0; j < 14; j++) {
const r = radAt(SEPTA[j]);
if (r < 34) continue;
const p = shellPt(SEPTA[j], 0.55, 1.0);
brush.circle(p[0], p[1], max(1.1, randomGaussian(r * 0.016, 0.4)), true);
}
// --- the knot at the pole, where the record runs out of room
brush.fillBleed(0.3, "out");
brush.fill(INK, 148);
brush.circle(PX + 2, PY - 1, 6.0, true);
brush.fill(INK, 96);
brush.circle(PX - 7, PY + 6, 4.2, true);
brush.fill(SEPIA, 96);
brush.circle(PX + 11, PY + 8, 7.6, true);
brush.fill(SEPIA, 72);
brush.circle(PX - 14, PY - 9, 6.2, true);
// --- pigment pooling where septum meets wall, and two heavier drops at the mouth
for (let j = 0; j < 11; j++) {
const s = SEPTA[j];
const r = radAt(s);
if (r < 42) continue;
pool(s + 0.04, OUT - 0.05 + random(-0.03, 0.02), max(2.0, randomGaussian(r * 0.038, 0.9)), 128 + random(-24, 42), INK);
if (random() < 0.6) {
pool(s - 0.025, IN + 0.05, max(1.6, randomGaussian(r * 0.03, 0.7)), 104 + random(-22, 34), INK);
}
}
pool(TH_AP - 0.2, 0.92, 9.5, 138, INK);
pool(SEPTA[0] + 0.06, 0.4, 6.8, 116, INK);
pool(1.02, 0.99, 5.4, 96, INK);
}