Seeds scattered on a grid, grown into their neighbours, and wired together with three concentric rings each.
Grow first, draw second
The piece is in two halves that do not overlap in time. PopulateCells decides the
whole network — which cells hold nodes, how large each is, and what connects to what —
and only then does the drawing loop walk the grid.
PopulateCells(cells, (int)(NSeeds * GridSize * GridSize),
(int)(NHoles * GridSize * GridSize),
(int)(GrowCycles * GridSize * GridSize), …);
Three counts, all expressed as fractions of the grid so they scale with it: seeds,
holes, and grow cycles.
A sixth of the cells drawn. Every node here is already at its final size.
Seeds, holes, growth
Seeds are cells picked at random to hold a node. Holes are cells picked to hold
nothing, and to keep holding nothing — they are placed deliberately in the network,
so the gaps in the finished network are as much a decision as the nodes.
Grow cycles then spread nodes outward: a cycle picks an occupied cell and fills a
neighbour, unless that neighbour is a hole. Running 0.52 × GridSize² cycles on a grid
that started with 0.08 × GridSize² seeds is what turns Aurora Canopy’s scatter into something that
reads as connected.
The order matters. Growth establishes the connected structure first, then holes cut
intentional breaks through it to keep the finished network from becoming a solid mass.
A third. The connectors are visible now: each runs to a neighbour at one of the four
cardinal points, and only where both cells are occupied.
Three rings per node
Every node is drawn as three concentric bands — outer, inner and core — each hatched at
its own spacing:
float? outerSpacing = outer == 0 ? null : PenSize * outer;
float? innerSpacing = inner == 0 ? null : PenSize * inner;
float? coreSpacing = core == 0 ? null : PenSize * core;
A spacing of null means that band is left empty. The three Color properties are
integers rather than colours: they are multipliers on the pen size, so 2 means hatch
at twice the pen spacing — half as dark — and 0 means do not hatch at all.
There is also a guard that stops two bands taking the same value:
int inner = TryInnerColor == outer ? (TryInnerColor + 1) % 3 : TryInnerColor;
which is why the properties are named Try…: the preset asks, and the code makes sure
the three bands stay distinguishable.
Half.
Three quarters. Every mark in the piece is a hatch line clipped against everything
already drawn — hidden-line removal rather than painting over — so the drawing order is
also the depth order.
The full network.
Five renderings
| Rendering | Grid | Seeds | Holes | Grow | Character |
|---|
| Aurora Canopy | 11 | 0.08 | 0.05 | 0.52 | fine mint branching on deep teal |
| Ember Relays | 7 | 0.16 | 0.16 | 0.28 | broad, isolated coral stations |
| Indigo Archipelago | 14 | 0.18 | 0.20 | 0.22 | small lavender islands |
| Cobalt Trunklines | 9 | 0.04 | 0.02 | 0.68 | long blue runs on warm paper |
| Saffron Switchyard | 12 | 0.12 | 0.08 | 0.42 | open gold rings in a busy network |
The ratio of grow cycles to holes is what decides whether the result reads as a circuit
or as an archipelago: more growth and fewer holes gives long connected runs, the reverse
gives isolated clusters.
Determinism
One Random is created per draw, seeded from the preset, and consumed entirely inside
PopulateCells — seeds first, then holes, then grow cycles. The drawing consumes none
of it, which is why a partial drawing is a strict prefix. The generator is
System.Random, which the project’s determinism policy flags as not guaranteed stable
across .NET versions.