← All devlog posts

Cairn

The art is code

Every character in Cairn is drawn by JavaScript as SVG line work, with no image files anywhere in the project. Why a part is a function, why the colors are site tokens, and what the first gate caught.

There are no image files in Cairn. Every character is drawn in the browser by JavaScript as SVG line work, from a small record of numbers. I decided that on day one, and this morning the first gate told me it was worth building on.

A part is a function

By my design estimate, a pixel art version of Cairn would have needed two to four hundred drawn parts. Because the art is code, I can make a part a function instead. One nose generator with parameters for bridge, length, tip and flare replaces twenty authored noses. I put the whole library at roughly forty generators. The Phase 0 face already works this way. It takes an eye spacing, a brow angle and a nose length as numbers from zero to one and maps each into a narrow range. The rule, in the code's own comment, is that every value must look like a decision somebody made, never like a slider being abused.

I hung all of it on a rig of 96 by 128 units with sixteen named anchor points, crown to feet. The rig file calls itself immutable, because if those numbers change, every drawn part becomes invalid. The bust in a portrait frame is the same artwork at a tighter view box, so there is no second set of art.

Heraldry is where code beats a person outright. A coat of arms is a rule system, a division of the field plus a charge, so the generator draws one deterministically from an integer seed. A character carries the same arms in everyone's browser, and no art ships for it.

Colors are site tokens

Colors enter the code in one file, and only as CSS custom properties. Part functions never name a color. They name an ink role, meaning how heavy a line is, and a fill role for what sits behind it. Every value those roles resolve to is a live token from this site's own stylesheet, and nothing can be added without changing the site's palette.

That is what makes the look I chose work. I compared three treatments side by side, engraving, heavy contour and one called Constructed, and picked Constructed. It is thin technical line with the rig still showing, anchor points in gold and construction guides in hairline. A blueprint of a person.

The objection to it was that it reads cold, and the design turns that around. A fresh character is all scaffolding and clinical line. A veteran's guides have receded, her line has thickened and warmed, and her marks have built up. A dead character left unrecovered drops to hairline, the anchors come back as severances, and the marks and heraldry do not fade. Constructed starts cold so that warmth is something the dungeon does to her. All three states are the same paths, differing only in a state field that the palette resolves into ink weight, ink color and scaffold opacity.

The same reasoning is why I use no game engine. Godot's web export cannot read CSS custom properties, and Phaser, Pixi and Three.js solve problems a turn based SVG game does not have.

What the gate caught

The Phase 0 gate rendered one character record three times, fresh, veteran and lost, from committed code. Fifty four shapes across nineteen layers, and no literal color anywhere in the output. This morning I recorded the verdict as GO, with no change to the rig, the layer stack or the record.

The first version of that gate page did not show what its captions claimed. Three custom properties were written onto the SVG root and never read, so the veteran rendered with full scaffolding under a caption saying it had receded. Review caught it before I judged the look on it.

About an hour after the verdict, writing the workshop plan, I found a second field of the same kind. Each character's own palette was written, stored and validated in Phase 0 and never read, so two characters that differed only in color rendered the same. I fixed it before noon and added a rule to the spec that every field in the record must be read by something.

I left three caveats open with the verdict. Faces are weakest, one character is not a survey, and the tavern board shows thumbnails. A whole character in a URL covers the twenty seed test that went after the second.

← All devlog posts