Es erscheinen laufend neue Modelle und Modellversionen mit starkem Fokus auf Coding und auf lang laufende, agentische Aufgaben. Aber wie findet man für sich heraus, wie gut sie wirklich funktionieren?
Fast alle Modelle werden zusammen mit Benchmarks veröffentlicht, nur eben nicht immer mit denselben. Die Ergebnisse bleiben dadurch schwer vergleichbar, und gerade bei populären Tests steht die Frage im Raum, ob benchmaxxed wurde, das Modell also gezielt auf gute Werte in bestimmten Tests trainiert wurde.
Es schadet also nicht, eine eigene kleine Test-Suite zu haben. Bleibt die Frage, was darin überhaupt eine gute Aufgabe ist. Ist sie zu groß, entstehen tausende Zeilen Code, die sich kaum noch reviewen lassen, und am Ende stellt man nur fest, ob das Ganze läuft. Ist sie zu klein und zu bekannt, kennt das Modell die Lösung längst, und man testet das Training statt der Problemlösungskompetenz. Ganz trocken sollte sie außerdem nicht sein, schließlich schaut man sich die Ergebnisse regelmäßig selbst an.
Deshalb haben wir uns gefragt, wie gut LLMs eigentlich im Schlösserknacken sind.
Nicht in der Realität, natürlich. Das Gothic Remake hat ein inzwischen recht berüchtigtes Lockpick-Minispiel, das viele Spieler frustriert hat. Entsprechend schnell ist eine Bandbreite an Lösungen entstanden, vom kompletten Überspringen bis hin zu Online- und Auto-Solvern.
Wie das Minispiel funktioniert
Ein Schloss besteht aus beweglichen Scheiben, 4 bis 7 an der Zahl, mit je 7 Löchern und einem Stift. Die Position einer Scheibe ist das Loch, in dem ihr Stift gerade steckt, also eine Zahl von 1 bis 7. Der Spieler wählt eine Scheibe aus und bewegt sie um ein Loch nach links oder rechts. Offen ist das Schloss, wenn alle Stifte im mittleren Loch (Position 4) stehen.
Klingt erst einmal einfach, allerdings sind die Scheiben miteinander verbunden: Bewegt man eine Scheibe, verschieben sich andere um ein Loch mit, entweder in die gleiche oder in die entgegengesetzte Richtung. Diese Kopplungen sind weder symmetrisch noch transitiv.
Im Detail: Wenn Scheibe 1 also Scheibe 3 mitbewegt, heißt das weder, dass Scheibe 3 umgekehrt auch Scheibe 1 bewegt, noch dass dabei die Kopplungen von Scheibe 3 mit auslösen. Ein Zug betrifft immer nur die Ausgangsscheibe und deren direkte Abhängigkeiten.
Dazu kommt eine weitere Einschränkung. Ein Zug ist komplett ungültig, sobald dabei irgendeine Scheibe über ihren Anschlag hinaus müsste.
Im Detail: Steht eine Scheibe also schon auf Position 7 und müsste auf 8, passiert gar nichts und das ganze Schloss bleibt stehen. Das gilt sowohl für die Ausgangsscheibe als auch für die gekoppelten. Und auch wenn eine Scheibe gar keine Abhängigkeiten hat, ist der Versuch, sie auf Position 0 oder 8 zu bewegen, ein ungültiger Zug.
Bei vielen Scheiben mit verschachtelten Abhängigkeiten wird das schnell unübersichtlich, und Spieler sitzen durchaus eine Weile an einem einzelnen Schloss.

Warum es ein gutes Testproblem ist
Wie der Beschreibung zu entnehmen ist, braucht das Minispiel weder Glück noch Geschick. Es gibt keine Zufallskomponente, das Problem ist vollständig algorithmisch lösbar. Trivial ist es nicht, extrem schwierig aber auch nicht. Ein paar Fallstricke gibt es trotzdem, etwa das Erkennen von Zyklen oder die Frage, wie man hinreichend schnell feststellt, dass ein Schloss überhaupt nicht lösbar ist.
Dazu kommt, dass das Modell den Lösungsansatz erst aus einer Beschreibung der Regeln ableiten muss. Es muss also die passende Problemklasse erkennen, einen geeigneten Algorithmus auswählen und ihn dann auch korrekt implementieren.
Der eigentliche Test
Für den Test haben wir ein einfaches JavaScript-Modul ohne weitere Abhängigkeiten schreiben lassen. Der Code sollte möglichst überschaubar bleiben, aber grundsätzlich wiederverwendbar sein. Eine Web-App oder auch nur ein größeres CLI-Tool führt schnell zu Boilerplate-Code, der uns beim eigentlichen Problem wenig weiterhilft. Bereitgestellt haben wir die Problembeschreibung, so wie sie auch in diesem Artikel steht, dazu das Eingabeformat für die Schlösser:
{
"locks": [
{
"id": "some-id-string",
"holes": 7,
"target": 4,
"plates": [
{
"position": 2,
"dependencies": [
{ "plate": 3, "direction": "s" }
]
},
{
"position": 1,
"dependencies": [
{ "plate": 1, "direction": "o" },
{ "plate": 4, "direction": "o" }
]
},
{
"position": 6,
"dependencies": []
},
{
"position": 3,
"dependencies": [
{ "plate": 1, "direction": "s" }
]
}
]
}
]
}
Ein Schloss hat eine id, die Anzahl der Löcher pro Scheibe (holes), das Zielloch (target) und die Liste seiner Scheiben (plates). Scheiben und Positionen werden ab 1 gezählt, position ist das Loch, in dem der Stift zu Beginn steckt. Offen ist das Schloss, wenn alle Stifte auf target stehen, im Spiel also auf der Mitte. Unter dependencies steht, welche anderen Scheiben mitlaufen, wenn diese Scheibe bewegt wird: plate ist der Index der gekoppelten Scheibe, direction gibt mit s für same oder o für opposite an, ob sie sich in dieselbe oder in die entgegengesetzte Richtung bewegt. Eine leere Liste heißt, dass die Scheibe frei läuft.
Festgelegt haben wir außerdem, dass der Output eine kürzeste „Lösungsanleitung” für das Schloss enthalten soll, ebenfalls in JSON. Gibt es mehrere Zugfolgen mit der minimalen Länge, reicht eine beliebige davon. Das genaue Format haben wir den Modellen überlassen, um zu sehen, was sie daraus machen. Die Anzahl der Scheiben und Löcher sollte das Modul aus dem Input ableiten, statt sie fest zu verdrahten. Zum Schluss noch ein minimaler Wrapper, der den Input lädt, das Modul aufruft und die Lösung auf die Konsole schreibt. Ist ein Schloss nicht lösbar, sollte stattdessen ein Fehler-JSON mit einer passenden Begründung zurückkommen.
Die Ergebnisse
Getestet wurden Claude 5 Opus (xhigh), GPT5.6-Sol (xhigh), Gemini3.6-Flash (high) und als eher „lower end”-Vergleich Kimi K2.6 über das Web-Interface (Kimi Code und die stärkeren Modelle waren zum Testzeitpunkt gerade nicht verfügbar).
Aus algorithmischer Sicht sind die Ergebnisse nicht besonders spannend. Eine Breitensuche (BFS) ist die naheliegendste Lösung für dieses Problem, und genau die haben alle Implementierungen gewählt, jeweils mit derselben Begründung: Der maximale Zustandsraum des Minispiels ist mit 7⁷ = 823.543 Zuständen hinreichend klein. Zusätzlich schenkt einem der Algorithmus die Lösung für die potentiellen Probleme gleich mit:
- Da alle Züge die gleichen Kosten haben, ist die gefundene Lösung garantiert die kürzeste
- Wird der ganze Graph durchlaufen, ohne auf die Zielkonfiguration zu stoßen, ist das Schloss garantiert nicht lösbar
- Für gewöhnlich führt man ohnehin ein
visited setmit, damit sind Zyklen einfach zu erkennen
Auch beim Output-Format herrschte weitgehend Einigkeit. Alle vier geben die Zugfolge als Liste aus, mit Scheibe und Richtung je Zug, dazu die Zuganzahl und ein Flag für die Lösbarkeit. Die Unterschiede liegen vor allem in der Ausführlichkeit und in der Benennung der Felder. Stellvertretend das Format von Claude:
{
"id": "gekoppelt-4a",
"holes": 7,
"target": 4,
"plateCount": 4,
"start": [5, 1, 6, 4],
"statesExplored": 1103,
"solvable": true,
"moveCount": 5,
"moves": [
{ "step": 1, "plate": 2, "direction": "right", "offset": 1, "positions": [5, 2, 6, 4] }
]
}
Ähnliches gilt für den Fehlerfall.
Im Detail gibt es aber doch viele Unterschiede. Die Lösungen sehen nicht „voneinander abgeschrieben” aus, sondern sind eigenständige Antworten auf dasselbe Problem, jede mit ihren eigenen Schwächen.
Wie wir geprüft haben
Kurz zur Prüfung, denn ohne sie wäre der interessanteste Befund gar nicht aufgefallen. Ein Solver, der seine eigene Ausgabe mit seiner eigenen Zugfunktion nachrechnet, beweist nämlich nichts: Steckt der Fehler im Regelverständnis, steckt er in der Lösung und in der Prüfung gleichzeitig.
Also lief alles gegen eine unabhängig geschriebene Referenz. Eine eigene Breitensuche bestimmt die tatsächliche Mindestzuglänge je Schloss, und jede ausgegebene Zugfolge wird Zug für Zug nachgespielt: Ist jeder einzelne Zug nach den Regeln erlaubt, und stehen am Ende wirklich alle Stifte auf dem Ziel? Dazu kamen ein paar gezielt gebaute Grenzfälle, die in den Beispieldaten fehlten, sowie Laufzeit und Spitzenspeicher auf dem teuersten Fall im Spielrahmen, also einem unlösbaren Schloss mit 7 Scheiben zu 7 Löchern, bei dem alle 411.772 erreichbaren Zustände durchlaufen werden müssen.
Die Einreichungen im Einzelnen
Claude 5 Opus: als einzige mit eigenem Prüfskript
Die defensivste Einreichung und die einzige mit einem unabhängigen Prüfskript, das die Spielregeln ein zweites Mal implementiert. Damit belegt sie ihre Korrektheit, statt sie zu behaupten.
Auch sonst ist die Fehlerbehandlung zu Ende gedacht. Der Solver wirft nie Exceptions, stattdessen liefert jeder Fehlerweg ein Ergebnisobjekt mit eigenem Code (INVALID_INPUT, UNSOLVABLE, SEARCH_LIMIT). Die Notbremse gegen zu große Schlösser hängt an den tatsächlich besuchten Zuständen statt an der Größe des Zustandsraums. Und jeder Schritt der Ausgabe führt die Positionen nach dem Zug mit, sodass die Anleitung ohne Nachrechnen lesbar bleibt.
Bezahlt wird das beim Speicher. Mit 182 MB im Spitzenfall braucht die Lösung elfmal so viel wie GPT5.6-Sol. Schuld ist die Warteschlange: Sie hält Objekte aus Zustand und Schlüssel und gibt abgearbeitete Einträge nie frei. Daher kommt auch der Faktor vier in der Laufzeit.
GPT5.6-Sol: die schnellste Suche im Feld
Die sauberste Suchschleife von allen vier. Durchgehend flache typisierte Arrays für Vorgänger, Zug und Warteschlange, also praktisch kein GC-Druck. Zustände werden in einen wiederverwendeten Puffer entpackt, statt sie für jeden Zustand neu anzulegen, und der Zieltest sitzt beim Erzeugen statt beim Entnehmen, sodass die Suche abbricht, sobald das Ziel auftaucht, statt die angefangene Ebene noch fertig auszubauen. Dazu echte Unit-Tests über node --test.
Die Strenge, die den Code so aufgeräumt macht, schlägt an anderer Stelle zurück. Selbstbezügliche und doppelte Kopplungen lehnt der Solver hart ab, obwohl die Aufgabenstellung beides nirgends verbietet und die anderen drei klaglos damit umgehen.
Interessanter ist die zweite Schwäche, weil sie fast dasselbe Muster hat wie Claudes Notbremse, nur an der falschen Größe. stateCount <= 0x7fffffff begrenzt den Zustandsraum, nicht den Speicher. Ein Schloss mit 11 Scheiben, das mit einem einzigen Zug offen ist, kostet dadurch 2,48 GB und 1,9 Sekunden, während die anderen drei in 0 ms antworten.
Gemini3.6-Flash: gut dokumentiert und trotzdem falsch
Zweitschnellste Einreichung, sparsamster Speicherverbrauch gleichauf mit GPT5.6-Sol und das mit Abstand beste README zum Algorithmus. Trotzdem steckt hier der gravierendste Befund, denn es ist die einzige Lösung, die eine falsche Antwort mit voller Überzeugung ausliefert.
Der Solver prüft die Eingabe überhaupt nicht. In Kombination mit exakt bemessenen typisierten Arrays wird daraus stille Datenkorruption. Eine Position außerhalb von 1..holes ergibt einen Index jenseits der Arraygrenze, JavaScript verwirft solche Schreibzugriffe wortlos, und beim Dekodieren wird der Wert wieder in den gültigen Bereich zurückgefaltet. Für ein Schloss mit einem Stift auf Position 9 von 7 Löchern meldet der Solver solved: true und drei Züge, von denen schon der erste regelwidrig ist.
Eine einzige Bereichsprüfung hätte das verhindert. So ist es die unangenehmste Fehlerart überhaupt: keine Exception, keine Warnung, nur ein falsches solved: true.
Kimi K2.6: 19 von 20 Sekunden in einer einzigen Zeile
Der klarste und kürzeste Code des Feldes, rund 90 Zeilen, sofort lesbar und fachlich richtig. Der Kommentarblock im Kopf ist die beste knappe Algorithmus-Erklärung im ganzen Vergleich, korrekt zu Zustandsraum, Nicht-Transitivität und der Ganz-oder-gar-nicht-Regel. Und dann steht da queue.shift().
Auf einem Array mit rund 400.000 Einträgen ist jedes Entnehmen O(n). Isoliert nachgemessen:
| Variante | Zeit |
|---|---|
wie eingereicht (shift plus String-Schlüssel) |
20.328 ms |
Kopfzeiger statt shift |
1.327 ms |
| Kopfzeiger und ganzzahlige Schlüssel | 483 ms |
shift() allein kostet also 19 der 20 Sekunden, und const state = queue[head++] behebt es.
Auch hier fehlt die Eingabeprüfung, mit milderen Folgen als bei Gemini. Dazu kommt als einzige der vier Einreichungen ein echter Regelfehler. Die Anschläge werden nach jeder einzelnen Kopplung geprüft statt einmal über die Summe. Bei Kopplungen, die sich gegenseitig aufheben, sieht der Solver ein Zwischenergebnis von 8 bei 7 Löchern, hält den Zug für ungültig und findet einen längeren Weg, in einem konstruierten Fall drei Züge statt einem.
Was der Vergleich zeigt
Der Algorithmus war nie das Problem. Alle vier finden für jedes lösbare Beispielschloss eine beweisbar kürzeste Zugfolge, erkennen das unlösbare als solches und setzen auch die beiden Stellen korrekt um, vor denen die Aufgabenstellung ausdrücklich warnt: die fehlende Transitivität der Kopplungen und die Ganz-oder-gar-nicht-Regel am Anschlag. Wer nur die mitgelieferten Daten durchlaufen lässt, sieht vier fehlerfreie Ergebnisse.
Die eigentlichen Unterschiede zeigen sich bei der Eingabeprüfung, und zwei von vier prüfen gar nichts. Damit ist auch klar, in welche Richtung man sich im Zweifel irren sollte: GPT5.6-Sol lehnt gültige Eingaben ab, was ärgerlich, aber sofort sichtbar ist. Gemini akzeptiert ungültige und antwortet trotzdem.
Die Messwerte dazu, alle aus demselben Lauf unter Node 26 auf Apple Silicon:
| 14 gültige Schlösser | komplex gekoppelt (22 Züge) | 7×7 unlösbar | Spitzen-RSS | Eingabeprüfung | |
|---|---|---|---|---|---|
| GPT5.6-Sol | 48 ms | 40 ms | 96 ms | 17 MB | vollständig, zu streng |
| Gemini3.6-Flash | 77 ms | 62 ms | 166 ms | 17 MB | gar nicht |
| Claude 5 Opus | 164 ms | 151 ms | 382 ms | 182 MB | vollständig |
| Kimi K2.6 | 667 ms | 470 ms | 22.596 ms | 192 MB | gar nicht |
Zwischen der schnellsten und der langsamsten Einreichung liegt auf dem 7×7-Lauf Faktor 235, über alle Schlösser hinweg Faktor 14, bei identischem Algorithmus und identischem Ergebnis. Die teuerste Zeile war dabei kein Denkfehler, sondern ein harmlos aussehender Aufruf aus der Standardbibliothek.
Und Schutzgrenzen gehören an die richtige Größe: GPT5.6-Sol begrenzt den Zustandsraum und alloziert deshalb 2,48 GB für ein Schloss, das mit einem Zug offen ist, Claude begrenzt die besuchten Zustände und erledigt denselben Fall in 0 ms.
Die Details der Implementierungen kann man im dazugehörigen GitHub-Repo einsehen.
Was der Test nicht zeigt
Der Vollständigkeit halber, weil die Zahlen sonst mehr Gewicht bekommen, als sie tragen: Es ist je ein einzelner Durchlauf pro Modell, ohne Nachfragen und ohne zweiten Versuch. Die Ergebnisse charakterisieren diese vier Dateien und nicht die Modelle allgemein. Ein zweiter Anlauf kann anders ausfallen.
Die ursprünglichen Beispieldaten waren außerdem wohlwollend: Sie enthielten keinen einzigen ungültigen Fall, und wäre nur gegen sie geprüft worden, hätten alle vier bestanden. Sämtliche gravierenden Befunde stammen aus nachträglich konstruierten Grenzfällen. Und der Zustandsraum ist winzig. Die Performance-Unterschiede sind zwar messbar, bei der Nutzung des Tools selbst würde man sie aber bis auf Kimi nicht bemerken.
Der zweite Anlauf: eine verbesserte Lösung
Des Spaßes halber haben wir zum Abschluss eine fünfte Implementierung gebaut, diesmal mit Kenntnis aller vier Ergebnisse. Sie ist kein gleichrangiger Teilnehmer, sondern die Zusammenführung dessen, was funktioniert hat: flache typisierte Arrays und Zieltest beim Erzeugen von GPT5.6-Sol, die Fehlerbehandlung, das unabhängige Prüfskript und die mitgeführten Positionen von Claude, die vorberechneten Delta-Vektoren von Gemini, der Anspruch an Lesbarkeit von Kimi.
Dazu kamen ein paar Ideen, die in keiner der vier steckten, etwa eine lineare Zustandskodierung, bei der ein Zug den Zustandscode nur noch um einen vorberechneten Betrag verändert. Das Ergebnis sind 63 ms auf dem 7×7-Lauf bei 11 MB Spitzenspeicher, also nochmal ein gutes Drittel schneller als die schnellste Einreichung und ein Drittel sparsamer als die sparsamste.
Fazit
Alle Modelle haben lauffähigen Code produziert, der die Problemstellung richtig angeht und gültige Eingaben korrekt löst. Tendenziell haben die Frontier-Modelle besser abgeschnitten, und zwar vor allem im Umgang mit dem, was die Aufgabenstellung offengelassen hat. Bei Gemini führte das sogar zu einer fehlerhaften Ausgabe, die sich selbst als Erfolg meldete.
Dadurch rückt erneut die Spezifikation in den Vordergrund: Hätte sie die unzulässigen Eingaben explizit aufgezählt und eine Eingabeprüfung verlangt, wäre die Gemini-Lösung vermutlich anders ausgefallen. Interessant ist außerdem, dass trotz quasi identischem Prompt und recht klarem Lösungsweg vier eigenständige Lösungen entstanden sind.
Abschließend bleibt anzumerken, dass keine der Lösungen perfekt war. Gerade wenn es auf Performance, sparsame Speicherverwaltung oder saubere Eingabeprüfung und Fehlerbehandlung ankommt, sollte weiterhin jemand sorgfältig und kritisch auf den Output der Modelle schauen.
Weiterlesen
- Coding-Modelle im April 2026: Update zur aktuellen Generation — die Modelle aus diesem Test und ihre offiziellen Benchmark-Werte im Überblick.
- Schneller programmiert, schlechter gewartet? Was Studien über KI-Code zeigen — was die Studienlage zu Qualität und Wartbarkeit von KI-Code sagt, passend zum Befund über die Eingabeprüfung.
- LLM-Modellfamilien im Überblick: Herkunft, Open Weights und europäische Optionen — wo Anbieter wie Moonshot AI einzuordnen sind, deren Modell hier mitgelaufen ist.
Sie möchten wissen, wie gut KI-Werkzeuge in Ihrem konkreten Umfeld funktionieren? Wir helfen dabei, eigene Testfälle zu bauen, Modellausgaben belastbar zu prüfen und Reviews so aufzustellen, dass Schwächen wie eine fehlende Eingabeprüfung auffallen, bevor sie in Produktion landen. Sprechen Sie uns an, wir freuen uns auf den Austausch.
Das Titelbild dieses Artikels wurde mit einem KI-Bildgenerator erstellt.