#1 Ein deterministischer Kern, vom Compiler erzwungen
· Nick
Zu sehen gibt es noch nichts, also geht es in diesem ersten Eintrag um den Teil, den niemand sehen wird: den Simulationskern. Das Spiel soll bis zu 1.000 Insassen und Mitarbeiter mit 20 Ticks pro Sekunde laufen lassen und erklären können, warum jemand etwas getan hat. Das funktioniert nur, wenn dieselben Eingaben immer dieselbe Welt ergeben. Ich habe beschlossen, das vom ersten Tag an zu einer Eigenschaft zu machen, die die Maschine prüft.
Wo ich stehe
Ich bin im ersten Meilenstein, dem Kerngerüst. Die Simulation ist eine reine .NET-Bibliothek ohne Godot-Code, und der Godot-4-Client öffnet bisher eine leere 3D-Szene. Es gibt noch kein Weltmodell, keine Kacheln, keine Figuren, keine Spielstände. Das Budget von 1.000 Figuren ist ein Ziel; gemessen habe ich in dieser Größenordnung noch nichts.
Der Tick
Zeit ist im Kern eine ganzzahlige Anzahl von Ticks, 20 pro Sekunde. Gleitkomma-Zeit gibt es nicht. Ein Tick hat drei Phasen: Befehle von außen anwenden, die Systeme in einer Reihenfolge ausführen, die an einer einzigen Stelle festgelegt ist, dann aufgeschobene Änderungen übernehmen und den Zähler erhöhen. Entkommt ein Fehler aus einem Tick, wird die Welt als fehlerhaft markiert und hält an. Heute ist die Liste der Systeme leer; die Schleife existiert, damit das erste System einen Platz hat.
Befehle und ein Protokoll
Die Welt ändert sich nur durch Befehle. Ein Befehl ist ein kleiner Datensatz mit einer festen Typ-ID und einer handgeschriebenen Binärkodierung. Für jeden Befehl kodiert die Welt ihn, versieht ihn mit Tick und laufender Nummer, prüft ihn (nur lesend), schreibt ihn ins Protokoll und wendet ihn erst dann an. Abgelehnte Befehle werden ebenfalls protokolliert, was bei Fehlerberichten helfen sollte. Seed plus Protokoll soll einen Lauf exakt reproduzieren. Das Replay-Dateiformat ist noch nicht geschrieben.
Zufallszahlen
Die Zufallszahlengeneratoren habe ich selbst geschrieben (SplitMix64 und xoshiro256**) und das Random der Standardbibliothek im Kern verboten. Die Welt hat vier benannte Ströme (Befehle, Bedürfnisse, KI, Ereignisse), die aus dem Seed abgeleitet und im Weltzustand gespeichert werden, also mitgespeichert und mitgehasht. Für Arbeit, bei der die Reihenfolge keine Rolle spielen darf, gibt es einen zustandslosen, schlüsselbasierten Generator über Domäne, Entität und Tick. Die Ausgaben für Seed 42 sind festgeschrieben, denn das ist im Grunde das Replay-Format: Ändert man den Generator, ist jedes alte Replay kaputt.
Ein Hash der Welt
Ein kleiner reihenfolgeabhängiger Hasher faltet Seed, Tick, Zufallsströme und eine Prüfsumme des Befehlsprotokolls in 64 Bit. Zwei Läufe, die gleich sein sollten, lassen sich mit einer einzigen Zahl vergleichen. Im Moment deckt er nur diese Teile ab, weil die Welt nichts anderes enthält. Der nächste Schritt ist, die Hashes während eines Laufs alle N Ticks zu vergleichen.
Nichtdeterminismus als Build-Fehler
Ich wollte mich nicht darauf verlassen, mir die Regeln zu merken. Der Kern wird mit Analyzern für verbotene APIs gebaut: Systemzeit, ungeseedete Zufallszahlen, zufällige GUIDs, randomisierte String-Hashes, Threads und Parallel-Helfer, SIMD und kulturabhängige String-APIs lassen den Build scheitern. Weitere Prüfungen stellen sicher, dass es keinen veränderlichen statischen Zustand gibt, und durchsuchen den kompilierten Code nach allem, was durchgerutscht ist. Mein Rechner nutzt ein russisches Gebietsschema mit Dezimalkomma, ein guter Grund, kulturabhängige APIs zu verbieten.
Dann bin ich weitergegangen und habe die Standard-Hash-Collections (Dictionary, HashSet und Co.) aus dem Kern verbannt, denn ihre Iterationsreihenfolge ist eine klassische Quelle für Abweichungen. Der Kern bekommt Arrays, Listen und zwei kleine eigene Collections mit dokumentierter Reihenfolge. Lückenlos ist das nicht: Eine API, die eine Hash-Collection über ein generisches Interface zurückgibt, kann trotzdem durchschlüpfen, und ein Teil des Schutzes greift nur in Tests.
Wie es gebaut wird
Auch die Werkzeuge rund um den Code sind automatisiert, und dieselben Skripte laufen unter Windows und macOS. Den allgemeinen Ansatz beschreibt Wie es entsteht.
Als Nächstes
Noch offen in diesem Meilenstein: das Weltmodell mit Etagen, Spielstand- und Replay-Dateien, der Determinismustest und ein Szenario-Runner ohne Oberfläche mit einer Benchmark-Basislinie. Danach kommt die Bauansicht in Godot. Siehe die Roadmap.