Ein Seed ist eine Person

Dieselbe Zahl ergibt immer dasselbe Gesicht. Das klingt nach einer Bequemlichkeit und ist in Wahrheit die härteste Zusage dieses Projekts – sie zwingt jeder späteren Änderung eine Regel auf.

Ein Generator für alles

Der ganze Zufall dieses Projekts sind vierzehn Zeilen. Kein Math.random() irgendwo im Zeichenpfad, keine Bibliothek: mulberry32 ist klein, schnell und – das ist der Punkt – deterministisch.

Die benachbarten Seeds

Symptom: Neun Köpfe nebeneinander sehen sich ähnlicher, als sie sollten – ähnliche Kopfbreite, ähnliche Merkmale, sichtbare Streifen im Raster. Ursache: Ein Rasterplatz bekommt blattSeed + index. Benachbarte Seeds sind hier also der Normalfall, nicht die Ausnahme – und die ersten Werte einer LCG-artigen Quelle korrelieren bei benachbarten Startwerten sichtbar. Lösung: Acht Züge wegwerfen. macheZufall ruft den Generator achtmal, bevor er ihn herausgibt; danach sind Seed 4711 und Seed 4712 zwei Fremde.

Alles hängt an einer Rohquelle

range, int, pick, chance und weighted sind Bequemlichkeiten, und jede zieht ihre Zahl aus derselben Funktion r. Das ist keine Sparsamkeit, sondern die Voraussetzung für den nächsten Absatz: nur ein Strom, der an einer Stelle entspringt, lässt sich mitschreiben und noch einmal abspielen.

Mitschreiben und abspielen

Gebraucht wird das an genau einer Stelle: die Nase wird über dem Bart ein zweites Mal gezeichnet. Sie muss dabei dieselbe Linie sein, nicht eine ähnliche – der Stift zieht seinen Verzug aus dem Strom, und ein zweiter, frischer Strom ergäbe eine Nase, die um einen Haaresbreite danebenliegt. Zwei Nasen, die fast übereinanderliegen, sehen aus wie ein Druckfehler.

Die andere Lösung wäre ein eigener Strom für die Form gewesen. Sie wurde verworfen: ein eigener Strom verschöbe den Hauptstrom nicht – aber er müsste irgendwo herkommen, und jede Ableitung, die aus dem Hauptstrom zieht, verschiebt alles danach.

Das Band läuft aus

Symptom: Beim zweiten Durchgang liefert der Strom undefined, und die Zeichnung stürzt mitten im Gesicht ab. Ursache: Der zweite Durchgang zieht mehr Werte als der erste – etwa weil eine Form je nach Zweig unterschiedlich viele braucht. Lösung: abspielen wiederholt das Band von vorn, statt zu scheitern. Die Wiederholung stimmt dann ohnehin nicht mehr; ein Absturz wäre trotzdem die schlechtere Antwort.

Forks: eigene Ströme ohne Nebenwirkung

Ein FNV-Hash über das Label, verrechnet mit dem Seed. Gleicher Seed plus gleiches Label ergibt immer denselben Strom, und zwei verschiedene Labels ergeben zwei unabhängige. Damit kann jeder Zeichenschritt würfeln, ohne irgendjemanden zu verschieben.

Jeder Nebenstrom hat einen Namen. strom(seed, label) ist der Griff dafür: ein ablegerSeed und ein macheZufall in einem.

StromWoWofür
'layout'core/dna.tsdie vier Layout-Gene
'handschrift'styles/klar/index.tsdie Kopfdaten des Stils: Augen-Jitter, Seite, Taktversatz, Bartlage, Punktmaß, Stern, Herz, Wangenbogen
'zacken'schale.tsdie Zacken des Ansatzes
'schlaefenfall'schale.tswie weit das Haar an der Schläfe fällt
'afroBeulen'schale.tsdie drei Beulen, die aus der Kugel Haar machen
'afroSorte'afro.tswelcher der sechs Archetypen gezogen wird
'afroRauung'afro.tsdie feine Rauung des Rands
'afroFransen'afro.tsdie Fransen
'afroKringel'afro.tsdie Kringel in der Masse
'straehnen'masse.tswelche Strähne radiert ist und welche ihren dunklen Zwilling bekommt
'locken'haar.tsdie Wickel am Rand der Lockenwolke
'dutt'haar.tsder Knoten des Dutts
'fuelle'haar.tsdie Fülle der Frisur
'hutform'hut.tsdie Sorte Hut und ihre Maße
'feder'app/blick.tsSteifigkeit, Atem, Lidschlag, Plappern
'name'app/namen.tsdie Silben unter dem Kopf
'marke'app/logo.tsdie Setzfehler über dem Blatt

Bis vor kurzem stand an diesen Stellen seed ^ 0x64757474. Die Zahlen waren lesbare Wörter in Hex – 0x64757474 ist „dutt" –, und das war kein Scherz, sondern Buchführung: zwei Ableger mit derselben Konstante wären derselbe Strom gewesen, und eine doppelte Zahl fällt beim Nachschlagen schneller auf als ein doppelter Zufallswert. Nur war es dieselbe Buchführung, die ablegerSeed schon führte – von Hand geschrieben, in einer Schreibweise, die man erst übersetzen muss. Wer einen neuen Strom brauchte, musste eine freie Zahl suchen. Jetzt schreibt er das Wort hin.

Die Zieh-Reihenfolge ist Teil des Vertrags

Neue Felder immer ans Ende. Ein Feld, das mitten im Strom gezogen wird, verschiebt alles danach – und damit jedes bestehende Gesicht. Ein Seed ist die Adresse einer Person; wer die Reihenfolge ändert, zieht alle um.

Genau so ist es passiert: kragen, zeichen, pupille und zierrat kamen später dazu. Inhaltlich gehören sie zwischen die anderen Merkmale, im Code stehen sie hinter der Palette – mit einem Kommentar, der sagt, warum.

Dieselbe Regel gilt eine Ebene tiefer. Der klare Stil zieht in kopfdaten seine eigene Streuung, und auch dort kommt Neues hinten dran: der Stern des Sternauges und das Herz auf dem Mund stehen am Ende, weil ein Kopf, der sie nicht trägt, sich sonst allein davon geändert hätte, dass es sie gibt.

Warum das Layout aus einem eigenen Strom kommt

Die Merkmalswahl braucht das Layout: welche Augenform gewählt werden darf, hängt davon ab, wie weit die Augen auseinanderstehen. Käme das Layout aus dem Hauptstrom, müsste es also vor den Merkmalen gezogen werden – und jede spätere Änderung an den Layout-Genen verschöbe sämtliche Merkmale sämtlicher Seeds. Der Fork macht die beiden Ströme unabhängig: am Layout darf gearbeitet werden, ohne dass jemand ein neues Gesicht bekommt.

Der Katalog ist Daten, kein switch

Welche Varianten es gibt, wie wahrscheinlich sie sind und wo sie nicht hinpassen – das steht in core/katalog.ts als Liste. Ein switch über Kategorien wäre dieselbe Information, verteilt auf so viele Stellen, wie es Leser gibt.

nurBei ist hart: passt die Lage nicht, fällt die Variante raus. lieber ist weich: passt sie, verdreifacht sich das Gewicht. Und wenn alles herausfällt, gewinnt die ungefilterte Liste – eine Kategorie darf nie leer sein, denn weighted auf einer leeren Tabelle hat keine Antwort.

Verträglichkeit kommt nach der Ziehung

Das ist der Grund, warum Kopfbedeckungen im Diagramm oben seltener sind, als ihr Gewicht sagt: Sie werden nachträglich gestrichen. Eine Kappe über einer Haarmasse gibt einen Wulst am Ansatz – nur der Hut darf über die Schale, weil seine Krempe den Ansatz deckt, und auch er nicht über Zöpfen oder Dutt, die oben herausstehen. Augenringe neben rosigen Backen geben einen Ausschlag.

Gestrichen wird nach der Ziehung, nicht davor. Wer die Kandidaten vorher filtert, ändert die Zahl der gezogenen Werte – und verschiebt damit jeden Seed, sobald sich eine Regel ändert. Ein stiller Wurf, der weggeworfen wird, kostet nichts.

Die Namen

Ein Gesicht mit Namen wird angesehen, ein Gesicht ohne Namen wird überflogen. Die Silben sind so gewählt, dass sie in keiner Sprache, die hier jemand liest, etwas bedeuten – und gelegentlich, in gut jedem fünften Fall, kommt eine dritte dazu. Gleich lange Namen in einer Reihe sehen aus wie ein Formular.

Auch die Namen hängen an einem eigenen Strom (mulberry32(seed · 2246822519), vier Züge zum Aufwärmen). Sie sind Beschriftung, nicht Identität – aber sie sind genauso determiniert wie das Gesicht, sonst hieße derselbe Kopf bei jedem Neuzeichnen anders.