From head to sheet
Nine to thirty heads on one surface, each in its place, each at the same size – and any one of them large in the middle at any moment. That is less administration than it sounds: the sheet is a grid, a scale, an order and a number.
The grid
The column count jumps at four fixed thresholds – 560, 900, 1300, 1800 pixels. Not a formula but a table: a formula would eventually hit a width at which it proposes two columns or eleven, and neither is a sheet. The rows then follow from a single condition – the cells should stay roughly square.
When in doubt, one column more rather than large heads with air around them. A head standing alone in a flat tile on a wide screen looks lost; six small heads in a row look like a sheet.
The seed of a cell is blattSeed + r · spalten + c.
Neighbouring cells therefore have neighbouring seeds – exactly the
case for which the generator in the chapter
Randomness throws away eight draws. Without
that warm-up there would be rows of relatives here.
Space requirement against platzIn
The style says what a head needs (raum(dna)), the cell
says what it can offer (platzIn). Both compute in head
units, so that they are comparable at all.
Downwards nothing is measured: the name stands there, and no hairdo reaches that far. Upwards and sideways it does – the afro is round and distinctly wider than the skull, the knot of a bun sits on top, and a hat brim juts out past every head.
If a hairdo demands more than its cell can offer, it is not that one head that gets smaller but the whole sheet. Nine heads at nine scales look like a fault; a slightly smaller sheet looks like intent.
Usually the result is 1. The cell is dimensioned so that
the usual hairdos fit; the factor only takes effect for the largest
afro spheres – and then for everyone.
The drawing order is part of the correctness
A head is not assembled but stacked. There is no occlusion computation, no depth buffer and no masks between the strokes – every stroke may paint over what lies beneath it. The order is therefore not a sequence but a statement about what lies in front of what. (The one exception stays inside a stroke: the cheek arc clips the eye it belongs to, see Features.)
For a long time it was written down nowhere. It was the order of
seventeen calls in zeichneGesicht, and whoever moved one
of them two lines up moved an occlusion – with nothing telling them
they had just touched a contract. Today every stroke names its
layer, and the layers stand as a list:
The strokes are collected, sorted stably by layer and then drawn. Stable means: within a layer whoever was collected first still comes first – otherwise the contour would lie under its own surface and be gone.
| Layer | Why there |
|---|---|
hals | Right at the back. The head sits on it and covers the join. |
hinter-dem-kopf | Braids, the afro sphere. They are drawn before the head surface and half covered by it – that is the whole trick. |
ohren | The far ear sits at the side of the skull, so under its surface. |
kopf | The lid over everything at the back – and the contour above it, or the edge of the surface would eat into the line. |
zugewandtes-ohr | The near ear lies in front of the cheek: when the head turns, its anchor travels behind the silhouette, and the head surface bit off its root. Which ear lands here is said by its normal – not by its side in the picture. |
haut | Cheeks, eye rings, hatching, forehead lines: colour and pale secondary lines on the skin, under all the strokes. |
bart | Under the nose, in the order too. Whatever overlaps the nose, it covers. |
fernes-auge | The eye on the far side. Which one that is is said by its depth – not by the sign of the rotation. |
nase | It has volume and stands free – so it covers the eye behind it. |
nahes-auge | Drawn in front of the nose, it would erase this one too. |
mund | Over the beard, under the rest. |
frisur | Hair and headwear: the shell lies on the skull, not under it. |
aufgesetzt | Earring, plaster – on the head, but not part of it. |
sternaugen | The pure star eyes (funkeln) and nothing else: not an eye shape but a mark on the head – bigger than any eye, in its own colour. Under a hairdo it would sit there clipped. The brow stays down with the face and is covered by the star. |
davor | In front of the face and therefore above everything – including the cap, which sits on the skull and not in front of it. |
Symptom: With a turned head a piece is missing from the near eye, as if it had been cut into. Cause: The nose is not a line but a shape with a surface. Drawn in front of both eyes it covers not only the far one but the near one too. Fix: Three layers interlock: far eye, nose, near eye. Which of the two is the far one is decided by the depth of the eye fields, not by the sign of the rotation – and a test holds it fast: over four hundred seeds the eye drawn last must be the one with the larger z.
The close-up is a number
No second element, no modal, no second canvas. fokus.t
runs between 0 (the head sits in its cell) and 1 (it stands in the
middle), and the location is computed from it.
Because every frame is redrawn anyway – the heads follow the pointer –
this is the simpler way and incidentally the smoother one:
t approaches its target by a fixed share of the remaining
distance each frame. That is "fast away, soft in" in both directions,
and it can be reversed mid-movement without anything jumping.
The veil over the grid is paper-coloured, not grey. The sheet steps back instead of a curtain falling in front of it – and at the end it covers completely, so that no grid shimmers through behind the large head.
The workshop: one head, sixteen dice
There is a way out of the large view. On
/werkstatt.html?kopf=118317 the same person stands alone
on the paper, with one die per feature beside them: the eyes without
the nose, the hairdo without the collar.
That this works at all is not a new computation but an old decision:
zeichneKopf takes a DNA and not a seed, and the style's
head-data cache hangs off the DNA object, not off its seed. A
hand-built DNA with a changed feature therefore gets its own shell,
its own palette and its own space requirement.
What is new is what an edited head IS. On the sheet a seed is the address of a person; re-rolling one feature leaves that address. A Wurf (a throw) is therefore the seed plus the little that deviates from it – and together they fit into one line again:
?kopf=118317&auge=stern&haar=igel&farben=2
Two kinds of deviation, written down differently.
Features have a vocabulary, so for them the value
itself is written. That is readable, short and stable: a rolled
feature is never recomputed and therefore cannot change behind your
back when another die falls next to it. Skull shape,
proportions, pose and colours have none – eight decimal
numbers in an address would be unreadable and broken at the first
rounding. For them the address says which throw counts; the value
comes from strom(seed, 'kopfform#3').
A feature, though, is more than its word. For every word the style
rolls a handwriting – how many spikes the hedgehog
has, which brim the hat, how full the bob lies – and that should fall
anew with every throw. If it hung off the word alone, this head would
own exactly ONE hat, and the die would show it again and again: not a
die, a switch. So behind the word, when needed, stands the
edition: haar=igel.3 is the hedgehog
from the third throw on the hairdo. The first edition carries no
counter, so every link from before shows the same head as before.
One die, one feature. What stands next to it stays – including the style's handwriting. That is why the eye die rolls shape, pupil, size difference and star spikes together (see streams), and why fresh proportions are discarded and drawn again until their layout fits the features already chosen.
There is one exception, and it stands here because it is one: the tone of the pure star eyes. It comes from a scale of its own and sits in none of the five colour genes. It therefore listens to TWO dice – the eyes', because it belongs to the star, and the colours', because on such a head it is the loudest colour in the picture. Once the colours have been rolled, that tone survives a new star shape.
The challenge is not the computation but the AMOUNT. A head consists of more than forty numbers and twelve features; as a list of sliders that would be a cockpit. Three decisions keep it small.
| Decision | Effect |
|---|---|
| Sixteen groups instead of forty values | Rolling happens on the level a person thinks in: "eyes", not "eye shape, pupil size, jitter, star spikes" |
| The picture is the menu | Six places on the head roll their feature: eyes, nose, mouth, cheeks, hairdo, collar. The anchors come from the layout and run through the same view as the drawing – a guessed second geometry would be wrong on every other rotation |
| The list is the second, full hand | Every group, every value in plain words, and the path for keyboard and screen reader – for both of them the canvas is an empty rectangle |
Not on the head are brows, marks, glasses, beard and trinkets: a hit zone for the brows NEXT TO one for the eyes would be a guessing game, since a tenth of a head lies between them. The headwear is missing for the same reason – it sits where the hairdo sits.
A die with nothing to offer is switched off and says why: over a hair
mass every headwear except "nothing" is blocked. The catalogue
supplies the reason (both
directions), the page supplies the sentence. The draw happens
WITHOUT the value that currently stands there – a die that shows the
same thing again looks broken – unless that value has a visible
handwriting: a star, a hat, a hairdo may come back, because in a new
edition they are a different star, a different hat. Which values
those are is a list in core/wurf.ts. And it draws FLAT: the
catalogue's weights are made for the sheet, where one head in nine
should wear a hat and "none" weighs 8 for glasses, 12 for beard and 9
for headwear. Whoever presses the beard die in the workshop wants to
see a beard; with the sheet's weights three throws in four would be a
throw into the void. Here every value counts the same; only the fit
to the face's lay stays preferred.
The way back leads to the sheet you came from: the link from the
sheet carries its seed along (&blatt=…), the
workshop keeps it in its address and hands it back with the head –
/?blatt=…&kopf=… builds exactly that sheet and opens
the head large right away. The switch between the two pages is a
cross-fade (@view-transition), and so that it fades onto
a finished picture, both pages load their script render-blocking in
the head.
And the language? The docs carry it in the PATH – /docs/
is German, /docs/en/ English – so that a link arrives at
its recipient the way it was sent. A document has an edition. The
workshop is not one: its address describes a HEAD, and that head is
the same in every language. So here the browser decides – German for
whoever has German set, English otherwise. To pin it down, append
?sprache=de or ?sprache=en; then it stands
in the address and travels with it.
The sheet: a piece of paper you can take with you
On the grid the paper is the WINDOW. Nine heads spread across whatever area happens to be there; a narrow window gives a different grid than a wide one. That is right for something you leaf through.
In the workshop a single picture comes into being, and one that leaves the browser. A picture whose format comes from the window is a screenshot once downloaded: tall on a phone, wide on a desk, and never what you saw. So the sheet has a format – DIN, portrait, height = width · √2 – and units of its own. It is 600 units wide no matter how large it appears; the caller sets the scale on the surface before drawing.
One function for screen and file. The screen calls it with the scale of the display, the saved file with a larger one – 1500 px wide, so f = 2.5. Two drawing paths would drift apart sooner or later, and precisely where nobody looks: in the downloaded picture.
Inside it the head still hangs on the style's space requirement – a hat needs more room than a bare skull – but no longer on the window size. The scale is therefore computed once per throw and not once per frame.
The name stands in the bottom margin, small, like a signature. It hangs on the SHEET, not on the chin: a name that travels with the hairdo sits lower under a hat than under a bare skull.
Edge and shadow are drawn – with the same pen as the chin and the same press that also sets the skin beside its own contour. A rectangle with an exact corner would be the only exact thing on a page made of hand-drawn lines, and a soft CSS shadow the only piece of photography. The shadow is therefore a second plate that does not register: the same outline, shifted a little, in a darker tone. The paper is clipped to that outline, otherwise the drawn edge would sit on an exact rectangle of paper and you would see both.
Four strokes, not one rectangle: the corners are sharp, and every stroke runs a little past them – the way a hand draws a box. It does not stop in the corner, it goes over it. A rounded outline looked like a stack of cards, and that is exactly what a sheet is not.
And the sheet does not BOIL. The lines of a face start afresh eight times a second because a face is alive; a piece of paper is not, and a trembling frame around a calm picture would be the frame you end up looking at.
| Belongs to the table | Belongs in the picture |
|---|---|
| Edge and shadow of the sheet | Paper, grain, vignette |
| The movement – gaze, blink, breath | The rest position: the pose of the DNA, open eyes, no glance aside |
| List, arrows, bar | The head and its name |
The second row is the more important one. The head in the workshop is
alive – it follows the pointer, blinks, breathes. A picture taken
mid-movement would give a different image for the same address on
every click, and that an address denotes a head is the promise
everything here hangs on. The file name is that same address in a
spelling a file system tolerates: ?kopf=118317&auge=stern
becomes tomu-118317-auge-stern.png.
And on a phone? There a single line stands instead of the sixteen, and two arrows page through them. A list longer than the device is tall is not an overview but a shaft: the head you are rolling would be out of frame while you scroll. WHICH line is meant is decided by the computation; WHETHER only one is visible is decided by the style sheet – the computation should not have to know how wide the window is. Tapping a feature on the picture leaves the line below already on that feature.
The names are not part of the picture
They lie as <span> over the canvas, in exactly the
place where the canvas would otherwise have painted them – the app
computes that location anyway, for the zoom.
Symptom: A name cannot be selected, cannot be
copied, cannot be searched, and a screen reader finds nothing on the
sheet but an empty rectangle.
Cause: Painted text is a picture.
Fix: Text stays text. The canvas carries an
aria-label, the names stand above it in the DOM.
The gain is not only the copying. It also gives the name in the close-up a movement of its own: it comes up a touch from below once the head has already settled, and it goes faster than it came. A name that took as long as the head would compete with it for attention.
On the sheet it is the other way round, and for
the same reason: that picture leaves the browser. A name that lives
only in the DOM would not arrive at its recipient – a head without an
address. There the name is painted, and the screen reader hears it in
the canvas's aria-label.
Two names, not one: wunsch is what should stand there,
gezeigt is what stands there right now. The app asks
afresh every frame – the same wish sixty times a second. Without that
separation a new fade-out would start in every frame, and the fade-in
would never get its turn; the name would stay half there forever.
Checking without a screen
The development build puts four handles on window.presse:
wuerfle(seed) sets a particular sheet,
schaue(x, y, s) advances the springs without a frame
loop, zellen() says which head sits where and what it
wears, and abzug(name) sends the sheet at full resolution
via POST /__abzug to the server, which files it under
abzuege/.
Symptom: An automated check sees heads staring
straight ahead – whether they follow the pointer can only be
claimed.
Cause: Without a visible page the browser stops
requestAnimationFrame. There is nobody to paint the
next frame.
Fix: schaue() advances the springs
itself in steps of 1/60 second and draws once. For the print the
names are painted into the canvas once – the next frame wipes them
away again.
The whole branch exists only under import.meta.env.DEV;
in the bundle it is removed. A handle that ships with the release is
no longer a testing aid but an interface.