Skip to the plate
FORMA PUBLIC DOMAIN GENERATIVE ATLAS / ED. 0.28
Plate 95, Grown Colour Pattern: a still of the growth / precedence order plate as the atlas renders it, in the lattices accent.

PL. 95  ·  LATTICES / GROWTH / PRECEDENCE ORDER

Grown Colour Pattern

Lawrence J. Mazlack, 1976

OPEN THE LIVE PLATE ▸

DEFINITION

weightⱼ = Σᵢ cᵢ·dᵢⱼ   over the precoloured points i
fill in ascending weight; at each point draw from the colours
compatible with every coloured neighbour, weighted by the matrix

NOTES

Every other way of making a coloured picture in 1976 drew a figure and coloured it afterwards. Mazlack inverted it: no figure, no line-drawing step, just colour arriving one cell at a time in an order decided before any of it is placed. Each cell is scored by its distance to the seeds — nearest first — and that ranking is the whole plan; it is computed once and never revised. Filling then asks only what the already-coloured neighbours will tolerate and draws from the survivors at random. The feathering is not in either rule. It is what happens when a colour that may only step to its neighbour on the wheel is carried outward by a fill order that sweeps outward: the colour random-walks along the radius, and the walk is visible as grain. Mazlack got here from crossword puzzles, which he had a machine building by the same precedence trick, and cast the results in clear plastic with glass marbles for the coloured cells.

PROVENANCE

Origin
L. J. Mazlack, “Digital Computer Based Sculpture Composed of Coloured Elements”, Computer Graphics and Art 1(2), May 1976, 18–24. The precedence technique is his own, from L. J. Mazlack, “Computer construction of crossword puzzles using precedence relationships”, Artificial Intelligence 7(1), 1976, 1–19
Not Wave Function Collapse
The resemblance to PL. 68 is real and shallow, and the difference is the point. WFC propagates — collapsing a cell strikes options from its neighbours transitively, and the next cell is whichever now has fewest. Nothing propagates here: the order is a static distance field fixed before the first colour lands, and a cell reads only its immediate neighbours. There is no entropy, no arc consistency, and a cell with no legal colour is not a contradiction to backtrack out of — it takes colour 0 and the sweep carries on. Greedy assignment on a precomputed order, forty years earlier and by a different road
Reconstructed
Three things the paper describes rather than defines, chosen here and recorded so the choices are on the record rather than hidden. The distance metric is unnamed, but its printed weighting field reads 3 2 1 0 1 2 3 4 5 6 through a seed and 3 2 1 1 1 2 3 4 5 5 one row above — three equal 1s stacked beside the seed is the max-norm’s square ring and nothing else’s, so Chebyshev. How several neighbours’ rows combine is “a combined list of weighted colours” with one worked case; summing the entries and excluding any colour that scores zero against any neighbour reproduces it. The weighted draw is his, left functional
Which matrix
The compatibility matrix is the published one for his full-scale piece — a band 2 6 4 6 2 sliding along the diagonal, colour 0 universal at weight 1, everything else forbidden. Not the colour wheel from his small worked example, which admits the opposing colour: measured, that reading renders uncorrelated speckle at any lattice size, because a colour compatible with its opposite can jump anywhere on the wheel and the grain never forms. Mazlack says so himself — the wheel example is “intended to illustrate the growth process, and does not describe a developed colour combination definition”
Standing
Public domain — a weighting formula and a compatibility table
Constants
The colour weights cᵢ are his fourteen published figures, 0.90 to 1.33. Seeds and wheel size are the shape dials: fewer seeds give longer fronds, a wider wheel a finer grain

HOUDINI · VEX

The same published mathematics as a Detail Wrangle body. Paste it into a Wrangle with Run Over set to Detail; every constant is the published value plus a tweak channel, so Create Spare Parameters gives a slider that starts where the paper does.

// FORMA — PL. 95 · GROWN COLOUR PATTERN — Lawrence J. Mazlack, 1976
//   weightⱼ = Σᵢ cᵢ·dᵢⱼ   over the precoloured points i
//   fill in ascending weight; at each point draw from the colours
//   compatible with every coloured neighbour, weighted by the matrix
// Paste into a Detail Wrangle (Run Over: Detail), no inputs needed.
// Written from the published mathematics, not adapted from any code.
// Constants arrive at their published values. Press the node's Create
// Spare Parameters button and every tweak becomes a slider — starting
// at 0, the published figure, and moving in the constant's own units.
// https://forma-gen.com/#plate=mazlack

float p_cells = 110 + chf('cells_tweak');     // lattice size · live 70 .. 220
float p_wheel = 13 + chf('wheel_tweak');      // colours on the wheel · live 7 .. 22
float p_seeds = 4 + chf('seeds_tweak');       // precoloured points · live 1 .. 9

// The plate's own colour: FORMA's LATTICES accent as a cosine ramp,
// brightest near t = 0 and t = 1, near-black around t = 0.5.
vector forma_ramp(float t){
  return set(
    0.46 + 0.5 * cos(6.28318530718 * (t + 0)),
    0.4185 + 0.4549 * cos(6.28318530718 * (t + 0.05)),
    0.1389 + 0.151 * cos(6.28318530718 * (t + 0.1)));
}

// Mazlack's grown colour pattern, one whole epoch in one cook: score every
// cell by Σ cᵢ·dᵢ over the precoloured seeds in the Chebyshev metric, fill
// in ascending weight, and at each cell draw from the colours every coloured
// neighbour tolerates, weighted by his published band matrix — greedy
// assignment on a precomputed order, nothing propagating, which is the whole
// difference from WFC two plates over. The tie-break jitter is folded into
// the weight exactly as the plate folds it (every weight step is ≥ 0.01, so
// 0.004 of jitter can only reorder exact ties), and the order comes from
// argsort where the plate sorts an index array. One point per coloured
// cell; colour 0 is Mazlack's white — the ground, so it emits nothing.
// The wheel maps onto the ramp's full period, cyclic onto cyclic — a field,
// where the trough reads as contour, not a sparse mark.
// Deterministic: seeds, jitter and draws are random(counted seed) on the
// plate's own first-epoch seed.
// waived: rate — it paces the plate's growth per frame, and a cook has no clock
int n = int(rint(p_cells));
int N = int(rint(p_wheel));
int ns = int(rint(p_seeds));

// his own fourteen colour weight factors, reused cyclically past fourteen
float CW[] = {1.33, 1.32, 1.25, 1.18, 1.10, 1.00, 1.00,
              1.00, 1.10, 0.90, 1.25, 1.22, 0.90, 0.90};

int rc = 1976;                    // seeded(1976 + epoch·7717) at epoch 0
int g[];
resize(g, n * n);
for (int i = 0; i < n * n; i++) g[i] = -1;

int sx[], sy[], scol[];
for (int k = 0; k < ns; k++){
    int x = 2 + int(random(rc) * (n - 4));  rc++;
    int y = 2 + int(random(rc) * (n - 4));  rc++;
    int c = 1 + int(random(rc) * (N - 1));  rc++;
    push(sx, x);  push(sy, y);  push(scol, c);
    g[y * n + x] = c;
}

// the precedence stack: weight every uncoloured point, argsort ascending
float wt[];
resize(wt, n * n);
int order[];
for (int y = 0; y < n; y++){
    for (int x = 0; x < n; x++){
        int i = y * n + x;
        if (g[i] >= 0) continue;
        float w = 0.0;
        for (int s = 0; s < ns; s++)
            w += CW[scol[s] % len(CW)] * max(abs(x - sx[s]), abs(y - sy[s]));
        wt[i] = w + random(rc) * 0.004;  rc++;
        push(order, i);
    }
}
float ow[];
foreach (int i; order) push(ow, wt[i]);
int rank[] = argsort(ow);

// his compatibility matrix: a 2 6 4 6 2 band on the wheel, colour 0
// universal at weight 1, everything else forbidden
int C[];
resize(C, N * N);
int W1 = N - 1;
for (int i = 0; i < N; i++){ C[i] = 1;  C[i * N] = 1; }
for (int i = 1; i < N; i++){
    C[i * N + i] = 4;
    int up = 1 + ((i - 1 + 1 + W1 * 2) % W1), dn = 1 + ((i - 1 - 1 + W1 * 2) % W1);
    int u2 = 1 + ((i - 1 + 2 + W1 * 2) % W1), d2 = 1 + ((i - 1 - 2 + W1 * 2) % W1);
    C[i * N + up] = 6;  C[i * N + dn] = 6;
    C[i * N + u2] = 2;  C[i * N + d2] = 2;
}

// the fill, in precedence order — the growth the plate spreads over frames
float wts[];
resize(wts, N);
foreach (int r; rank){
    int i = order[r], x = i % n, y = i / n;
    float total = 0.0;
    int any = 0;
    for (int c = 0; c < N; c++){
        int ok = 0;
        float sum = 0.0;
        for (int dy = -1; dy <= 1 && ok >= 0; dy++){
            for (int dx = -1; dx <= 1; dx++){
                if (!dx && !dy) continue;
                int nx = x + dx, ny = y + dy;
                if (nx < 0 || ny < 0 || nx >= n || ny >= n) continue;
                int q = g[ny * n + nx];
                if (q < 0) continue;
                int v = C[c * N + q];
                // one intolerant neighbour vetoes the colour outright
                if (!v){ ok = -1;  sum = 0.0;  break; }
                ok = 1;  sum += v;
            }
        }
        wts[c] = ok == 1 ? sum : 0.0;
        if (ok == 1){ total += sum;  any = 1; }
    }
    if (!any || total <= 0.0){ g[i] = 0;  continue; }   // his white, and the fallback
    float r2 = random(rc) * total;  rc++;
    int pick = 0;
    for (int c = 0; c < N; c++){ r2 -= wts[c];  if (r2 <= 0.0){ pick = c;  break; } }
    g[i] = pick;
}

for (int y = 0; y < n; y++){
    for (int x = 0; x < n; x++){
        int c = g[y * n + x];
        if (c <= 0) continue;     // colour 0 is the ground, not a colour
        // canvas y runs down; negated so the fronds feather as the plate shows them
        int pt = addpoint(0, set(float(x - n / 2), float(n / 2 - y), 0.0));
        setpointattrib(0, "Cd", pt, forma_ramp(float(c - 1) / float(N - 1)));
    }
}