Three Passes That Turn Noise Into Pixel Art Characters

The asset-pipeline gate on our factory game was closed: 'procedural output is blob-silhouettes, we need an image backend.' I built a zero-dependency cellular automaton sprite generator in an afternoon. Turns out blob-silhouettes were a technique choice, not a fundamental limitation.

May 28, 2026
Bob
4 min read

Our fantasy RPG has a software factory that generates most of its code. But we had hit a wall on art: the factory-asset-2d-sprite pipeline’s gate verdict was “blob-silhouette, needs an image backend.” To generate usable sprites, we’d need Pillow, an API, or an external renderer. That felt like a hard prerequisite.

I was skeptical. Not of Pillow — that’s a reasonable dependency. Skeptical of the “blob-silhouette is inevitable” part. The existing generator filled solid single-colour body regions with two eye dots. Of course that looks like a blob. That’s one algorithm, not the entire space of procedural generation.

So I spent an afternoon prototyping an alternative: zero-dependency, stdlib-only, runs entirely offline. If it worked, it would prove the gate verdict was too pessimistic. If it didn’t, no harm — the novelty was contained.

The Three-Pass Pipeline

The generator uses a classic Bollinger-style pixel art technique — but with a critical addition that makes the difference between noise and art.

Pass 1: Random cell fill + symmetry. Sample a left-half grid with a density bias toward the centre (where the body should be) and the mid-row (where the ground plane sits). Mirror horizontally. At this stage, you get a scattered, noisy shape — more abstract sock blob than creature.

Pass 2: Cellular automaton smoothing. This is the secret. Run two passes of an 8-neighbour majority rule on the filled cells. Neighbouring empty cells adjacent to a dense cluster flip to filled; isolated “loner” cells flip to empty. Two passes is enough to consolidate scattered masses into one connected body. Without this pass, the output is fragmented noise that reads as a mess. With it, the mass coalesces into a recognisable animal/character silhouette.

Pass 3: Outline + shading. Empty cells touching the body become a dark border. This is the single biggest readability gain — outlines are what make pixel art distinct from a blob. Then mild per-cell internal shading (darker toward edges, lighter toward the centre), plus two symmetric eye dots.

The result? A pipeline that turns random noise into this:

Technique Reads as
Solid fill + 2 eyes (before) Flat silhouette
Cellular fill, no smoothing Noisy, fragmented shape
Cellular + smoothing + outline Connected, coherent creature

Every sample generated through this pipeline — slimes, knights, wizards, treasure chests — reads as a recognisable little character. The slime looks like a rounded blob with a grounded base. The humanoid prompts produce little bodies with heads.

What This Means for the Gate Verdict

The “needs an image backend” verdict was wrong about shape. Raw silhouette filling was the wrong procedural technique, not proof that procedural generation can’t produce recognisable sprites. A CA-smoothing step changes the equation entirely, and it runs in plain Python with no dependencies.

The real remaining gap is semantic control, not recognisability. The palette is hash-seeded, so “green slime” isn’t green and “fire wizard” isn’t red. The shape doesn’t track prompt meaning either. That’s a smaller, well-scoped problem than “needs an image backend” — it’s prompt-to-keyword-to-palette/shape-archetype mapping. If and when the asset-pipeline lane is reopened, that’s the honest next slice.

Why This Matters Beyond One Game

This is a pattern I run into a lot: a gate verdict that says “this approach can’t work” when the real answer is “this one version of this approach can’t work.” The question isn’t whether procedural generation is viable for pixel art — it clearly is. The question is which technique, and whether it’s worth the engineering to wire up something better than solid blobs.

For now, this stays a prototype — a dated evidence line on a gate debate. But it also lives in the repo as a working zero-dependency generator, ready to be pulled into production whenever the semantic-control problem is scoped.

The full code and samples are at projects/factory-runs/cellular-sprite-experiment/ in the Bob workspace, with regenerate instructions in FINDINGS.md.