---
title: Sixteen Colours and a Registry
date: 2026-07-14
tags: [runawaydevil, ansi, art]
---
# Sixteen Colours and a Registry

The IBM PC text mode gave an artist sixteen foreground colours, eight
backgrounds, and 256 shapes. No pixels, no gradients, no partial transparency.
A cell was one character in one colour on one background, and that was the
whole medium. People made astonishing things with it for twenty years.

Everything drawn for this system works under the same rules. Here is what that
means in practice, and what the build does about it.

## The vocabulary is smaller than the palette

```text
█ full block     ▓ dark shade
▒ medium shade   ░ light shade
```

Those four are not textures — they are *mixing*. Light shade of white on black
is a grey that does not exist in the sixteen. Medium shade of bright blue on
blue is a blue that is not in the palette either. The entire tonal range of the
medium comes out of four characters.

The site's own pieces are held to the CP437 character set, and the validator
says so out loud when they wander:

```text
WARN  ornaments   27x4   outside CP437: ▪ ▫ †
```

Which is how `▪▫▪` became `■·■` and a dagger became `♦`. Worth knowing: CP437
has no tilde vowels. It has `ç`, it has `á`, it has no `ã`. That is exactly why
a Brazilian on a 1994 board read *cora‡Æo* instead of *coração* — and why the
webfont here is the "Plus" variant rather than the pure 437 one.

## The art is data, and that is deliberate

Original pieces live as typed data rather than as `.ANS` files on disk, so the
compiler and the tests can measure them with the same code that measures every
other line in the system. A piece 92 cells wide fails the build instead of
arriving crooked.

The format is not lost. `npm run art:export` writes each piece out as a real
`.ANS`: CP437 bytes, CRLF, a SAUCE record built from the registry, a
terminating SUB. They open in PabloDraw and pass through AnsiLove.

Getting that right took two bug fixes worth mentioning, because both destroy
art silently:

- `encodeCp437(' ')` returned `0x00`, not `0x20`. Byte zero also decodes to a
  blank, and it won the reverse lookup.
- Escape, carriage return and line feed were being encoded as `←`, `♪` and `◙`.
  In an art *buffer* the low bytes are glyphs; in a *stream* they are controls.
  Confusing the two turns every colour change in a file into a arrow character.

## Nobody's work goes up without their name

Every piece has an entry in `src/art/art-registry.yml`: author, handle, group,
year, licence, original filename, notes. A piece with no entry does not build.
When the terms are unclear the entry says `reference_only: true`, and the
validator keeps it out of the build, out of the gallery and out of `dist/`.

SAUCE records — the 128 bytes the scene stapled to the end of a file with the
artist's handle, group and date — are read on import and shown by
`art info <slug>`. They are the reason we still know who drew what, thirty
years later.

There is one rule with no exception: an artist's signature is never removed.
Not to fit a layout, not to fit the grid. If the signature does not fit in 80
columns, the piece does not go in.

## Why it still reads well

A constraint that severe forces an artist to think in shape and value rather
than detail. Same discipline as woodcut, as stencil, as any printing process
with a limited number of passes. Work made under those rules tends to survive
changes in display technology, because it never depended on the display being
good.

An ANSI drawn in 1993 on a CRT still looks correct on a 5K panel in 2026. Very
little else from 1993 can say that.
