Wie ich es gebaut habe.
Zugwunder ist von Grund auf in Swift geschrieben: keine Engine-Bibliothek darunter, kein Framework außer dem, was Apple mitliefert. Diese Seite ist der Rundgang durch das Innere, für neugierige Spieler und für Programmierer, die wissen wollen, worauf die Zahlen beruhen.
Warum von Grund auf
Weil eine Schachengine ein Programm ist, das man messen kann.
Ich baue Software, die auf meinen eigenen Rechnern läuft statt im Rechenzentrum anderer Leute, und eine Schachengine ist der ideale Prüfstein: Jede Idee lässt sich in ein paar tausend Partien schicken und gewinnt entweder Elo oder nicht. Mit dem Partieformular lässt sich nicht streiten. Zugwunder begann als Mattlotse, mein älterer Löser für Matt-in-n-Aufgaben. Am 25. September 2026 habe ich ihn in Zugwunder umbenannt, und in den Wochen danach sind daraus die Suche, die Bewertung, der Trainer, die Partien, das neuronale Netz und die beiden anderen Programme gewachsen, Version für Version, wobei ein Vergleich gegen die Vorversion entschied, ob eine Änderung blieb.
Alles ist Swift 6 mit strikter Nebenläufigkeit, gebaut mit dem Swift Package Manager, und hängt von nichts außerhalb von Apples Frameworks und der Swift-Standardbibliothek ab. Die einzige Ausnahme ist Fathom, eine kleine C-Bibliothek unter MIT-Lizenz, die Syzygy-Endspieltabellen liest; ein eigener Leser für dieses Format hätte nichts als Fehler hinzugefügt.
Das Brett
Bitboards, Zobrist-Schlüssel, eine sperrfreie Hashtabelle.
Die Stellung ist ein Satz 64-Bit-Bitboards, eines je Figurenart und Farbe, sodass „alle Felder, die ein Springer von hier angreift“ ein Tabellenzugriff ist und „alle Figuren, die dieser Turm angreift“ ein paar Bitoperationen. Der Zuggenerator erzeugt ausschließlich legale Züge, einschließlich Rochade, En passant und Umwandlungen, mit einem eigenen schnellen Pfad für Schlagzüge, den die Ruhesuche nutzt.
Jede Stellung hat einen 64-Bit-Zobrist-Schlüssel, der die Transpositionstabelle, die Erkennung von Stellungswiederholungen und den Bauernstruktur-Cache antreibt. Die Transpositionstabelle teilen sich alle Suchthreads ohne Sperren, und Mattbewertungen werden auf den Abstand zur Wurzel normiert, damit ein Eintrag, der in einer Tiefe gefunden wurde, in einer anderen richtig bleibt.
FEN und PGN haben eigene Parser und Schreiber. PGN behält Tags, Kommentare, Bewertungen und verschachtelte Varianten über einen vollständigen Umlauf, und die Prüfung erklärt, warum eine Stellung regelwidrig ist (zwei Könige nebeneinander, ein Bauer auf der Grundreihe, ein Rochaderecht ohne Turm), statt die Engine darin suchen zu lassen.
Die Suche
Principal Variation Search mit den üblichen Beschneidungen, jedes Teil gemessen, bevor es blieb.
Der Kern ist eine Alpha-Beta-Suche in ihrer Principal-Variation-Form mit iterativer Vertiefung und Aspirationsfenstern. Darum herum sitzt der übliche Werkzeugkasten einer modernen Engine, Stück für Stück hinzugefügt und nur behalten, wenn ein Vergleich einen Gewinn zeigte:
- Nullzug-Beschneidung, Reverse Futility Pruning, Razoring und Internal Iterative Reductions.
- Late Move Reductions und Late Move Pruning, Futility Pruning und Beschneidung nach statischer Abtauschbewertung (SEE).
- Zugsortierung nach Hashzug, Schlagzügen per SEE, Killerzügen, Countermoves und einer History-Tabelle mit Belohnung und Bestrafung.
- Ruhesuche für Schlagzüge und Umwandlungen, damit die Bewertung nur zu ruhigen Stellungen befragt wird.
- Lazy SMP: mehrere Threads durchsuchen denselben Baum mit unterschiedlichen Tiefen und teilen sich die Hashtabelle, auf bis zu 64 Kernen.
- Eine Zeiteinteilung, die früh aufhört, wenn der beste Zug stabil ist, und länger denkt, wenn er wechselt (die Option
SoftTime), und eine Hashtabelle, die über die Züge einer Partie hinweg mit einer Altersmarke behalten statt geleert wird.
Nicht alles hat überlebt. Die Bewertung mit wachsendem 50-Züge-Zähler gegen null zu skalieren und eine Correction History für die statische Bewertung verloren beide ihre Vergleiche und flogen wieder heraus. Die Versionshinweise halten verworfene Versuche genauso sorgfältig fest wie angenommene, damit eine Idee nicht aus Versehen zweimal probiert wird.
Die Bewertung
Ein kleines neuronales Netz, trainiert mit den eigenen Partien der Engine.
Die erste Bewertung war handgeschrieben: Figur-Feld-Tabellen, Königssicherheit und Bauernstruktur, mit dem Texel-Verfahren an den eigenen Partien der Engine abgestimmt. Sie steckt noch in der Binärdatei (UseNNUE false) als Vergleichsmaßstab. Die Bewertung, die heute spielt, ist ein NNUE, die Art Netz, die Stockfish zum Standard gemacht hat, hier aber aus den eigenen Daten der Engine trainiert:
- 768 Eingänge, einer je Figur, Farbe und Feld, aus der Sicht jeder Seite; zwei versteckte Akkumulatoren mit 256 Neuronen und Clipped ReLU; ein Ausgang.
- Gewichte, quantisiert auf 16-Bit-Ganzzahlen. Die Akkumulatoren werden beim Ziehen inkrementell aktualisiert, 16 Neuronen je SIMD-Vektor, sodass eine Stellungsbewertung ein paar hundert Ganzzahloperationen kostet statt eines vollständigen Vorwärtsdurchlaufs.
- Das Netz lebt in der Binärdatei als Swift-Quelldatei, die aus den trainierten Gewichten erzeugt wird; es gibt keine separate Datei, die verloren gehen könnte. Für Experimente lässt sich mit der Option
NNUEFileein anderes Netz laden.
Die Trainingsschleife ist so schlicht, wie sie klingt. zugwunder-datagen lässt die Engine gegen sich selbst und gegen Stockfish spielen und schreibt je Stellung einen 68-Byte-Datensatz: die Merkmale, Zugwunders eigene Suchbewertung und das Partieergebnis. Etwa vier Millionen Stellungen pro Stunde auf acht Kernen. Ein 150-zeiliges PyTorch-Skript trainiert das Netz auf einer Apple-GPU, embed.py verwandelt die Gewichte in Swift, und die nächste Version trägt das neue Netz. Die sechste Generation wurde mit 236,6 Millionen Stellungen trainiert; jede Generation wurde erst nach Vergleichen gegen ihren Vorgänger und gegen fremde Engines übernommen.
Was nicht hineinkam: kein Stockfish-Code, keine Stockfish-Gewichte, keine Stockfish-Bewertungen. Stockfish war Gegner und Maßstab, sonst nichts.
Endspielwissen
Berechnet, nicht heruntergeladen.
Für König und Dame, Turm oder Bauer gegen den nackten König sowie für die beiden Viersteiner Läufer und Springer gegen König und Turm gegen Läufer schätzt Zugwunder nicht: Es berechnet die exakten Tabellen beim Start selbst durch Retrograde-Analyse und nutzt die Symmetrien des Bretts, um die Arbeit klein zu halten. Die Viersteiner-Tabellen brauchen etwa anderthalb Sekunden im Hintergrund. Suche, Löser und Rätselgenerator lesen sie alle.
Für alles andere mit bis zu fünf Steinen gibt es die Syzygy-Tabellen: Zeig der Engine einen Ordner, und sie spielt diese Endspiele perfekt und nutzt die Urteile in der Suche nach jedem Schlagzug oder Bauernzug.
Der Rätselgenerator ist das kleinste Programm auf dieser Seite und mein liebstes. Er durchläuft die 64 × 64 × 64 Aufstellungen von drei Steinen in zufälliger Reihenfolge mit einer ungeraden Schrittweite, sodass jede Aufstellung genau einmal besucht wird, ohne Liste; behält nur legale Stellungen, deren Tabelleneintrag „Matt in genau n“ sagt; und prüft dann, dass genau ein erster Zug das erreicht. Die Stellung auf der Startseite stammt daraus.
Exakte Löser
Beweisbäume für Matt, Hilfsmatt, Selbstmatt und Patt.
Eine Spielsuche beantwortet „Was ist hier gut?“. Ein Problemlöser muss „Gibt es ein erzwungenes Matt in drei, und ist es eindeutig?“ beantworten, und das ist ein anderes Programm. Zugwunders Löser bauen UND/ODER-Beweisbäume: Bei den Zügen des Angreifers muss ein Ast funktionieren, bei den Zügen des Verteidigers muss jede Antwort beantwortet sein. Das Ergebnis ist der vollständige Lösungsbaum mit allen Verteidigungen, ein Urteil bewiesen, widerlegt oder innerhalb der Grenze unentschieden, und eine Warnung, wenn ein zweiter erster Zug die Aufgabe ebenfalls löst, denn für einen Problemkomponisten ist eine Aufgabe mit Nebenlösung eine kaputte Aufgabe.
Hilfsmatts (beide Seiten arbeiten zusammen, alle Lösungen werden aufgelistet), Selbstmatts (die Partei am Zug zwingt den Gegner, sie mattzusetzen) und Pattaufgaben nutzen dieselbe Maschinerie mit anderem Zieltest. Diese Löser sprechen direkt mit der Engine-Bibliothek statt über UCI, weil sie den Beweisbaum brauchen, nicht einen besten Zug.
Messen
Tausende Partien, und die Disziplin, ihnen zu glauben.
Das Projekt liefert seine eigenen Messwerkzeuge mit. zugwunder-match lässt zwei Engines aus einem Eröffnungsbuch gegeneinander spielen, jede Eröffnung mit beiden Farben, mit Uhren, Adjudikation und einem Sequential Probability Ratio Test (SPRT), der aufhört, sobald das Ergebnis statistisch klar ist. Eine Suchänderung muss diesen Test gegen den vorigen Build bestehen, bevor sie übernommen wird. zugwunder-rating verwandelt Vergleichsergebnisse gegen Stockfish auf mehreren UCI_Elo-Stufen in eine Maximum-Likelihood-Schätzung auf Stockfishs Skala.
Die Hauptzahl, 3065 ± 20 Elo für das aktuelle Netz, stammt aus 500 Partien gegen Stockfish bei UCI_Elo 3100 unter turnierähnlichen Bedingungen: 60 Sekunden plus 0,6 pro Zug, ein Kern, 256 MB Hash, Syzygy-Tabellen, wobei Engine-Binärdatei, Netze und Gegner in einem Referenzordner eingefroren und ihre Prüfsummen festgehalten sind. Eine zweite Serie gegen Arasan ergab ein stimmiges Bild. Derselbe eingefrorene Aufbau hatte das vorige Netz bei 3045 ± 20 gesehen. Diese 500 Partien sind die eingefrorene Referenzmessung; über die Wochen hat der Cluster der Rechner hinter dem Projekt Tausende weitere gegen Stockfish und andere Engines gespielt, für SPRT-Tests, Kalibrierung und Datensammlung. All das sind Schätzungen auf der kalibrierten Skala einer anderen Engine, keine offizielle Rangliste, und das sage ich überall, wo die Zahl auftaucht.
Die einstellbare Spielstärke der UCI-Engine wurde genauso kalibriert: mehr als 5.000 Partien gegen Stockfish 17.1 auf einem Linux-Rechner auf sieben Stufen, mit Konfidenzintervallen für jede, interpoliert zu einer UCI_Elo-Skala von 1100 bis 3000. Sie gilt als vorläufig, bis sie gegen einen zweiten Gegner und unter macOS geprüft ist, denn eine Kalibrierung gegen einen Gegner ist eine Kalibrierung gegen einen Gegner.
Unter den Vergleichen liegen 364 Swift-Tests über Engine, App und Wolfgang sowie 32 Protokolltests, die die echte zugwunder-uci-Binärdatei durch die UCI-Spezifikation von 2006 treiben: Pondern, searchmoves, Knotengrenzen, ein stop, das vor dem Suchstart eintrifft, Eingaben, die über mehrere Pipe-Schreibvorgänge verteilt sind. Der Protokolltest liegt jedem Paket bei, damit du ihn auf deinem eigenen Rechner ausführen kannst.
Die Apps
SwiftUI und AppKit, vier Sprachen, VoiceOver, ein sorgfältiger Updater.
Zugwunder und Wolfgang sind native macOS-Apps in SwiftUI, mit AppKit dort, wo SwiftUI nicht reicht (Fenster, Tastatur, Bildschirmfotos, Schlüsselbund). Das Brett zeichnet die App selbst, es unterstützt Drag-and-drop und vollständige Tastaturbedienung und liest unter VoiceOver Züge vor. Alle rund 900 Oberflächentexte gibt es auf Deutsch, Englisch, Französisch und Niederländisch, und die Sprache wird in der Fensterkopfzeile umgeschaltet.
Beide Apps bringen ihre eigene Kopie von zugwunder-uci mit und sprechen über UCI mit ihr, genau wie mit Stockfish: Die App sendet Ausgangsstellung und vollständige Zugliste, und der Engine-Prozess mit seiner Hashtabelle überlebt die ganze Partie. Nur die exakten Löser rufen die Engine-Bibliothek direkt auf. Eine App-Version ändert deshalb nie die Version der Engine, und die Engine lässt sich für sich allein testen.
Der Updater ist ein kleiner separater Helfer. Die App fragt einen JSON-Feed über HTTPS ab, prüft, dass der Download vom selben Host kommt, kontrolliert Größe und SHA-256, hängt das Disk-Image schreibgeschützt ein, prüft Bundle-Identifier und Developer-ID-Team, tauscht das Bundle atomar mit Sicherung aus und startet neu. Schlägt ein Schritt fehl, kommt die alte Version zurück.
Die neueste Funktion, die Erkennung einer Stellung aus einem Foto, ist die eine Stelle, an der die App mit einem Dienst spricht: Sie schickt das Bild über den von dir eingegebenen API-Schlüssel an Anthropics Claude, und nur, wenn du den Knopf drückst. Die Antwort wird als Vorschlag behandelt, als Schachstellung geprüft und zur Korrektur angezeigt. Eine frühere Fassung nutzte ein lokales Modell über Ollama; die jetzige nutzt Claude und sagt das neben dem Knopf.
Drei Plattformen, ein Build
macOS, Linux und der Browser aus derselben Quelle.
- macOS: Apple Silicon, mit Developer ID signiert, von Apple notarisiert und gestapelt. Das Release-Skript erhöht die Version, baut, signiert, notarisiert, schreibt den Update-Feed und ein Manifest mit Prüfsummen und bewahrt die Protokolle auf.
- Linux: auf dem Mac mit dem statischen Linux-SDK von Swift cross-kompiliert für x86_64 (mit AVX2) und ARM64. Das Ergebnis ist eine statisch gelinkte Datei, die keine Bibliotheken braucht, weshalb sie auf einem Raspberry Pi ebenso läuft wie auf einem Server.
- WebAssembly: gebaut mit dem Swift-SDK für WebAssembly gegen WASI und FoundationEssentials. Ein Web Worker führt die Engine aus, damit die Seite bedienbar bleibt; das komprimierte Modul ist kleiner als drei Megabyte. Die Spielseite nutzt es, und dasselbe Paket liegt bei den Downloads, falls du es selbst hosten willst.
Ein vollständiger Build führt die Swift-Tests, die Protokolltests und die Tests des Datensammlers aus, baut alle Pakete, signiert und notarisiert die macOS-Pakete und weigert sich zu veröffentlichen, wenn irgendetwas fehlschlägt. Der Ordner, den er erzeugt, enthält die Pakete, ein Manifest, eine SHA256SUMS-Datei und vierzig Protokolldateien.
In Zahlen
Gezählt am 2. November 2026.
| Engine | 10.200 Zeilen | Swift, ohne die 4.400 erzeugten Zeilen mit den Netzgewichten. |
|---|---|---|
| Zugwunder-App | 11.900 Zeilen | SwiftUI und AppKit, einschließlich der Oberfläche der Löser, des Trainers, des Updater-Clients und des Bildimports. |
| Wolfgang | 8.800 Zeilen | Die UCI-Schachoberfläche mit Turnieren und Engine-Verwaltung. |
| Werkzeuge | 1.400 Zeilen | Programme für Vergleiche, Tuning, Datenerzeugung, Bewertung, Analyse und Neubewertung, dazu die UCI- und WebAssembly-Frontends. |
| Tests | 7.500 Zeilen | 364 Swift-Tests, 32 Protokolltests und die Python-Prüfungen für den Datensammler. |
| Training | 150 Zeilen | Das PyTorch-Skript, das sechs Netzgenerationen hervorgebracht hat. |
| Veröffentlichungen | Über 30 | App-Versionen seit dem 25. September 2026, von Mattlotse 1.0.3 bis Zugwunder 1.1.15. |
Zeitleiste
Von einem Mattlöser zu drei Programmen.
Aus Mattlotse wird Zugwunder
Meine alte App Mattlotse, ein Matt-in-n-Löser mit vollständigen Beweisbäumen und Nebenlösungsprüfung, in Zugwunder umbenannt. PVS, Nullzug, LMR und die handgeschriebene Bewertung kommen am selben Tag.
Partien, Trainer, Dateien
Spielstärken, Schachuhr, Aufgabenbibliothek, PGN mit Kommentaren und Varianten, Hilfsmatt- und Selbstmattlöser, eigene Endspieltabellen, die UCI-Engine und das Match-Werkzeug mit SPRT, Lazy SMP.
Vier Sprachen, Netz v4
Deutsch, Englisch, Französisch und Niederländisch. Das vierte Netz (113 Millionen Stellungen) gewinnt 78 Elo gegenüber dem dritten.
Netz v5, erste Wertungsschätzung
170 Millionen Stellungen, +25 Elo. Tausend Partien gegen Stockfish 19 setzen die Engine auf 3006 ± 17 auf der CCRL-ähnlichen Skala.
Im Browser
Die Engine baut für WebAssembly und spielt auf f97.be ohne Installation.
Drei Pakete
App, Wolfgang und Engine werden getrennte Pakete mit einem gemeinsamen Release-Skript. Beide Apps bringen die Engine mit und sprechen UCI mit ihr.
Netz v6, UCI vollständig
Vergleiche über je 500 Partien gegen Stockfish und Arasan; 3065 ± 20 Elo. Jeder Befehl der UCI-Spezifikation umgesetzt und getestet.
Stärkekalibrierung
Mehr als 5.000 Partien gegen Stockfish 17.1 geben der Engine eine vorläufige
UCI_Elo-Skala von 1100 bis 3000.Rätselgenerator, Bildimport
Neue Rätsel aus den Endspieltabellen mit geprüft eindeutigem Schlüsselzug; Stellungen aus Fotos in allen vier Sprachen; Fensteraufnahmen.
Prüf die Zahlen selbst.
Jedes Paket enthält Prüfsummen und den Protokolltest. Die Match-Werkzeuge stecken im Werkzeuge-Archiv.