One Button Jump – ein Jump & Run mit C++-Server, Cluster und ganz viel Vibe Coding

One Button Jump – ein Jump & Run mit C++-Server, Cluster und ganz viel Vibe Coding

27. Oktober 2026 0 Von Willy Enns

Dieser Beitrag über One Button Jump wurde mit KI (Claude Code) erstellt und von mir gelesen, ergänzt und freigegeben.

Inhalt

Anfang 2020 habe ich hier mal über libGDX und libKTX geschrieben. Da hatte ich ein Tutorial durchgearbeitet und gedacht: Mal schauen, was dabei rauskommt.

Jetzt ist tatsächlich was dabei rausgekommen. Nur ganz anders als gedacht.

Ich habe ein Spiel gebaut. Es heißt One Button Jump und läuft unter onebuttonjump.de.

Weil das hier ein Tech-Blog ist, geht es nach einer kurzen Vorstellung vor allem um die Technik dahinter.

Startseite von One Button Jump mit dem Spruch „Ein Held. Viele Hände.“, einer Spielszene mit drei Buttons und den Knöpfen „Live zuschauen“ und „Jetzt spielen“
Die Startseite

Die Idee hinter One Button Jump

One Button Jump ist ein kooperatives Jump & Run. Es gibt genau eine Spielfigur. Aber jeder Spieler hat nur einen Teil der Buttons, meistens genau einen.

Einer läuft nach links. Einer läuft nach rechts. Einer springt.

Das klingt erst mal einfach. Ist es aber nicht. Wenn der Springer zu früh drückt oder der Rechts-Läufer nicht loslässt, liegt die Figur in der Grube. Und das Spiel zeigt dann auch, wessen Button schuld war. Mit Augenzwinkern, aber die Zähler stehen an den Namen.

Ich finde das ganz cool, weil man dadurch automatisch miteinander redet. „Jetzt springen!“ hilft mehr als jede Taste.

One Button Jump – Spielszene im Browser mit drei verbundenen Spielern: die Figur springt über eine Lücke, oben steht, wer am letzten Absturz schuld war, unten der eigene grüne Button „Rechts →“
Drei Spieler, eine Figur – und die Schuldfrage

So spielt man

Man startet eine Session, schickt den Link oder QR-Code an Freunde, alle klicken „Bereit“, los geht’s. Allein geht es auch, dann hat man alle Buttons selbst. Und wer gerade keine Gruppe hat, klickt auf „🎲 Mitspieler finden“.

Lobby von One Button Jump mit Fantasienamen, Zusatz-Buttons, Level des Tages, Schwierigkeit und den Knöpfen „Koop-Session starten“, „Solo-Session starten“ und „Mitspieler finden“
Die Lobby

Die Level werden beim Spielen erzeugt, jede Runde ist anders. Dazu gibt es Fluchtlevel mit einer Lava-Wand im Nacken, Mondlevel mit wenig Schwerkraft, jeden Tag ein neues „Level des Tages“ für alle, eine Rangliste, Zuschauen mit Applaus und vier Sprachen (Deutsch, Englisch, Französisch, Spanisch).

Was mir wichtig war: kein Konto, keine Cookies, keine Werbung, kein Tracking. Alles kommt vom eigenen Server, nichts wird bei anderen Anbietern nachgeladen.

One Button Jump – Aufgeklappte „Links zum Einladen“ mit Mitspiel-Link, Zuschauer-Link und einem QR-Code zum Scannen mit dem Handy
Einladen per Link oder QR-Code

One Button Jump und Vibe Coding

Oben auf der Seite steht: „Dieses Spiel ist zu 100 % mit Vibe Coding entstanden.“

Das stimmt auch. Den Code habe ich nicht selbst geschrieben. Geschrieben hat ihn Claude Code, die KI von Anthropic. Ich habe gesagt, was ich will, ausprobiert, gemeckert und entschieden.

So sieht mein Ablauf inzwischen aus:

  • Jede Idee ist ein Ticket in GitLab, mit Ziel, Entscheidungen und „Fertig wenn“.
  • Jedes Ticket bekommt einen eigenen Branch und einen Merge Request. Gemergt wird erst, wenn ich OK sage.
  • Größere Aufgaben laufen bei KI-Agenten im Hintergrund, mehrere gleichzeitig, jeder in einem eigenen Git-Worktree. Ich bleibe derweil ansprechbar und frage zwischendurch den Stand ab.
  • Gebaut und getestet wird alles in Docker. Einen C++-Compiler habe ich auf meinem Windows-Rechner gar nicht.

Klingt nach viel Prozess. Ist es auch. Aber sonst verliert man bei dem Tempo den Überblick.

Die Technik hinter One Button Jump

TeilTechnik
Spiel-ServerC++20, uWebSockets, eigene Physik
BrowserJavaScript mit Phaser 3, ohne Build-Schritt
AppGodot 4 (Android, Windows, Linux, Steam)
DatenbankPostgreSQL 18 mit Patroni und etcd
Davornginx
BetriebDocker Compose

Grob sind das gut 30 000 Zeilen C++, rund 17 000 Zeilen GDScript für die App und rund 6 000 Zeilen JavaScript im Browser.

One Button Jump – Schaubild: Browser und App verbinden sich per WebSocket mit nginx, dahinter der Eingang als Weiche, dann drei Spiel-Server in je einem Container auf einem Rechner; rechts der Datenbank-Cluster mit PostgreSQL-Leader, synchronem Standby und drei etcd-Mitgliedern
So hängt alles zusammen

Der Server rechnet alles

Der Server ist der Chef. Die Physik läuft nur dort. Browser und App zeichnen nur und schicken Tastendrücke.

  • Fester Takt: 60 Schritte pro Sekunde. Kommt der Server mal nicht hinterher, holt er bis zu 5 Schritte nach.
  • Ein Thread für das Spiel: Alle Runden eines Spiel-Servers laufen in einer Ereignisschleife. Datenbank, Log und Verzeichnis laufen in eigenen Threads, damit der Spieltakt nie auf sie wartet.
  • Deterministisch: Gleiche Eingaben ergeben genau den gleichen Ablauf. Deshalb kann der Server nach einem Tod die letzten Sekunden nachspielen und ausrechnen, wessen Button schuld war.
  • Senderate je Verbindung: Bei schlechter Verbindung schickt der Server nur jeden 2., 3., 4. oder 6. Spielstand. Den Stau misst er mit WebSocket-Pings.
  • Lastmodus: Wird der Spieltakt knapp, bekommen alle Sessions nur jeden 2. Spielstand, also 30 statt 60 pro Sekunde, bis es wieder entspannt ist.

Kompression statt Binärformat

Die Spielstände sind JSON. Mein erster Gedanke war ein Binärformat wie msgpack. Gemessen für einen Koop-Spieler pro Minute:

FormatDaten pro Minute
JSON roh496 KiB (100 %)
msgpack364 KiB (73 %)
JSON + deflate (damals im Browser)96 KiB (19 %)
JSON + deflate mit größerem Fenster53–68 KiB (11–14 %)

msgpack hätte mit Kompression nur noch 1 bis 2 Prozentpunkte gebracht. Also verworfen.

Dabei kam aber raus, dass uWebSockets eine Einstellung für die Kompression falsch auswertet (ein Bit-Vergleich statt eines Maskenvergleichs). Jede Einstellung landete beim kleinsten Kompressionsfenster. Mit einem kleinen Patch sind es im Browser 29 % weniger Daten.

Die App bekommt seitdem ebenfalls komprimierte Spielstände. In einer Solo-Runde sind das 4,7 MiB pro Stunde statt 27,3 MiB, also etwa 15 %. Auf dem Handy im Mobilfunk merkt man das.

Von Python nach C++

Angefangen hat der Server in Python. Anfang Oktober habe ich ihn komplett neu in C++ schreiben lassen. Gleiches Spiel, gleiche Level, nur viel schneller. Das Ruckeln nach einem Tod und beim Start einer Runde war damit weg.

Genaue Vergleichszahlen habe ich leider nicht aufgehoben. Aus der Erinnerung: Aus etwa 800 ms wurden etwa 8 ms. Das ist also eher ein Gefühl mit Zahl als eine Messung. Richtig gemessen habe ich dann den C++-Server, dazu gleich mehr.

Spannender ist ohnehin, wie sichergestellt ist, dass wirklich „gleiches Spiel, gleiche Level“ rauskommt. Dafür habe ich vorher Referenzdateien vom Python-Server eingefroren, sogenannte Golden Files:

WasWie viele
komplett erzeugte Level5 620
nachgespielte Runden (Physik-Schritte)250 (272 922 Schritte, 1 334 Tode)
Fälle zum Level-Charakter3 600
Fälle zur Schwierigkeit375
Fälle zum Zufallsgenerator78

Der C++-Server muss das alles Byte für Byte genauso erzeugen. Dafür bildet er drei Sachen aus Python exakt nach:

  • den Zufallsgenerator random.Random von CPython (Mersenne Twister),
  • Pythons Zahlenformat,
  • die Ausgabe von json.dumps.

Dazu wird die Physik mit -ffp-contract=off und ohne -ffast-math gebaut, damit der Compiler keine Rechenschritte zusammenfasst. Und gebaut wird auf Debian Trixie, weil die Referenzen mit dessen Mathe-Bibliothek entstanden sind.

Die Prüfung läuft bis heute bei jeder Änderung. Ergebnis muss immer „0 Unterschiede“ sein. Ohne so eine Absicherung hätte ich mich den Umbau nicht getraut.

Wie viel hält ein Server aus?

Das habe ich auf dem Live-Server gemessen. Die Last kam von meinem PC über nginx: Bots, gemischt aus Koop, Solo, Zuschauern und Spielersuche, halb Browser, halb App.

Zuerst mit einem Spiel-Prozess (Version 0.40). Der Container durfte zwei Kerne nutzen, der Spieltakt läuft aber auf einem.

VerbindungenSessionsSpieltakt p99RAM Spiel
2006410 ms39 MiB
30010013 ms44 MiB
40013315 ms52 MiB
51017522 ms62,5 MiB

Ein Schritt hat bei 60 Hz ein Budget von 16,7 ms. Bis 400 Verbindungen lief alles im Budget, stabil bis 510. Fehler gab es in keiner Stufe. Der Rechner selbst blieb dabei unter 40 % CPU. Die Grenze war also nicht der Server, sondern der eine Kern für den Spieltakt.

Danach habe ich den Spieltakt billiger gemacht: niedrigere Kompressionsstufe (etwa 20 % weniger Rechenzeit je Nachricht für 5 % mehr Daten) und Spielstände ohne JSON-Zwischenobjekte. Lokal sank der p99 bei 500 Verbindungen von 14,8 auf 11,8 ms.

Dann mit zwei Spiel-Prozessen (Version 0.41), wieder live:

  • mit vollen 60 Spielständen pro Sekunde stabil bis 475 Verbindungen,
  • im Lastmodus stabil bis 1 100 Verbindungen (927 Spieler, 373 Sessions, 5 Minuten lang bestätigt).

Bei 1 200 war mein PC als Lastquelle am Ende, nicht der Server. 0 Fehler, Server-CPU höchstens etwa 52 %.

One Button Jump – Liniendiagramm: Spieltakt p99 über der Zahl der Verbindungen mit einem Spiel-Prozess – 200 Verbindungen 10 ms, 300 Verbindungen 13 ms, 400 Verbindungen 15 ms, 510 Verbindungen 22 ms; gestrichelt das Budget von 16,7 ms pro Schritt bei 60 Hz
Spieltakt p99 mit einem Spiel-Prozess

Mehrere Spiel-Server

Ein Spiel-Server bleibt bewusst einfach: ein Thread für den Spieltakt. Mehr Spieler gibt es also nur mit mehr Spiel-Servern. Das Spiel ist deshalb komplett clusterfähig.

Kennzeichen: Jede Session hat eine ID. Das erste Zeichen ist ihr „Kennzeichen“, und jedes der 31 möglichen Zeichen gehört genau einem Spiel-Server. Daran sieht man sofort, wohin eine Anfrage gehört.

Verzeichnis in etcd: Die Spiel-Server melden sich beim Start selbst in etcd an und holen sich ihre Kennzeichen. Fällt einer aus, läuft seine Anmeldung ab. Ein neuer Spiel-Server ist nach ein paar Sekunden dabei, ohne dass ich eine Tabelle pflege oder nginx neu lade. Das war wichtig, denn ein nginx-Reload trennt laufende WebSockets.

Der Eingang: Vor den Spiel-Servern sitzt ein kleiner nginx, der nur eine Frage stellt: Wohin gehört diese Anfrage? Das fragt er einen Spiel-Server (per auth_request) und leitet dann weiter. Gefragt wird nur beim Verbindungsaufbau, nie pro Nachricht. Das kostet etwa 2,5 bis 3 ms pro Session-Aufruf. Beim Spielen merkt man davon nichts.

Interne Schnittstelle: Die Spiel-Server reden miteinander, zum Beispiel für die gemeinsame Live-Übersicht oder die Spielersuche. Jede Anfrage ist mit HMAC-SHA-256 signiert, mit Zeitstempel und Einmalwert gegen Wiederholung. Zwischen verschiedenen Rechnern kommt TLS dazu.

Die erste Fassung mit Verzeichnis hat übrigens 15 bis 30 % mehr CPU pro Spiel-Server gekostet. Gemessen, Ursache gefunden (TLS und die Weichen-Frage im Spieltakt-Thread), umgebaut, nochmal gemessen. Danach lag der Unterschied im Messrauschen.

One Button Jump – Schaubild in drei Schritten: Eine Anfrage zur Session „k7m2qx…“ kommt beim Eingang an; 1. der Eingang fragt die Weiche, wohin das Kennzeichen „k“ gehört; 2. die Weiche schaut im Verzeichnis (etcd) nach: „k“ gehört Spiel-Server 2; 3. der Eingang leitet zu Spiel-Server 2 weiter
Wohin gehört die Session?

Server wechseln, ohne dass Runden abbrechen

Das ist der Teil, auf den ich am meisten stolz bin.

Ein Spiel-Server kann sich abmelden (Drain):

  1. Er nimmt keine neuen Sessions mehr an.
  2. Laufende Runden dürfen noch eine Weile weiterlaufen.
  3. Danach hält er die übrigen Runden am nächsten Checkpoint oder am Levelende an und speichert sie in der Datenbank („gerettet“).
  4. Er gibt seine Kennzeichen samt den geretteten Runden an einen anderen Server ab.
  5. Browser und App verbinden sich neu und landen beim neuen Besitzer. Die Runde geht mit denselben Toden, derselben Zeit und denselben Checkpoints weiter.

Darauf baut das Rolling Update auf. Ein Skript macht Vorprüfung, Sicherung und Datenbank-Migration. Dann kommt ein Spiel-Server als „Kanarienvogel“ dran: abmelden, neues Image, beobachten. Erst wenn der sauber läuft, folgt der nächste. Lokal nachgestellt dauert das ohne Beobachtungszeit rund eine Minute, mit 0 verlorenen Runden.

Damit alte und neue Version solange nebeneinander laufen können, gibt es noch ein paar Regeln:

  • Versions-Sperre: Ein Update geht immer nur von Version N−1 auf N. Neue Funktionen, die alle Server verstehen müssen, schalten sich erst ein, wenn alle Server neu sind.
  • Alter Spielkern läuft weiter: Ändert sich die Physik oder der Level-Generator, steckt der Kern der Vorversion eingefroren mit im neuen Server. Eine gerettete Runde der alten Version spielt mit ihrem alten Kern zu Ende. Eine neue Runde nimmt den neuen.
  • Mischtest: Vor jedem Release läuft ein Test, bei dem das alte und das neue Image gemeinsam laufen.
  • Kennzeichen-Ausgleich: Bei der Generalprobe fiel auf, dass jedes Update Kennzeichen „verbraucht“. Nach etwa sechs Releases wäre Schluss gewesen. Seit Version 0.44 ist das korrigiert: Nach einem Update sind die Kennzeichen wieder gleichmäßig auf die Spiel-Server verteilt.

Live läuft das Spiel seit Version 0.43 in diesem Verzeichnis-Betrieb: ein Rechner mit drei Spiel-Servern, jeder in seinem eigenen Container. Ein weiterer Rechner ist vorbereitet, aber noch nicht nötig.

Das erste echte Rolling Update

Mit Version 0.44 war es dann so weit: das erste Rolling Update auf dem Live-Server. Zuerst kam ein Spiel-Server als Kanarienvogel dran, dann zehn Minuten beobachten, danach die beiden anderen.

Damit es nicht zu langweilig wird, liefen dabei sieben Last-Bots mit. Die wichtigere Probe waren aber zwei echte Handys.

Zwei Handys über LTE

Dafür habe ich mein Xiaomi 14 vom WLAN genommen und auf LTE gestellt. Das Samsung Galaxy S21 FE hing am Hotspot des Xiaomi. Beide gingen also über das Mobilfunknetz ins Internet, so wie unterwegs auch. Auf beiden lief die neue App 0.5.5, frisch installiert.

Die Koop-Runde habe ich gezielt auf den Kanarienvogel gelegt. Welcher Server eine Session bekommt, hängt am ersten Zeichen ihrer ID – die Session musste also mit dem richtigen Buchstaben anfangen. Dann haben beide Handys beigetreten: „Bunter Dachs“ hatte Links und Ducken, „Ruhiges Pony“ Rechts und Springen. Zu zweit, mit je zwei Buttons, quer durch Level 1.

Gerettet und fortgesetzt

Dann habe ich das Update gestartet. Zuerst kam auf beiden Handys der Hinweis „Server-Wechsel“. Weitergespielt haben wir trotzdem, bis zum nächsten Checkpoint. Dort hielt die Runde an: „Runde gespeichert“. Kurz „verbinde …“, und nach ein, zwei Sekunden waren beide Handys wieder drin – auf einem anderen Spiel-Server, mit ihren alten Buttons. Einmal „Bereit“ tippen, und es ging am selben Checkpoint weiter. Im Log stand: „fortgesetzt nach der Wartung (Level 2, 1 Tode, 4:59, 7/7 Leben)“. Gleiche Zeit, gleiche Tode, gleiche Leben. Die ganze Unterbrechung waren etwa 18 Sekunden.

Spannend war, dass die Runde jetzt auf einem Server mit der alten Version 0.43 lag. Als der eine Viertelstunde später dran war, musste sie noch einmal umziehen, diesmal auf einen Server mit 0.44. Wieder „Server-Wechsel“, wieder „Bereit“, wieder weiter. Am Ende waren wir zu zweit in Level 4, mit 2 Toden und knapp 20 Minuten auf der Uhr – zwei Server-Wechsel und eine neue Version mitten in der Runde, und das über Mobilfunk.

Genauso ging es mit einer Solo-Runde im Browser weiter, und eine Bot-Runde war nach 4,5 Sekunden wieder da. Die Bots hatten 0 Fehler.

Die Datenbank-Migration (eine neue Tabelle für die Neuigkeiten) lief in 0,2 Sekunden, mitten im Betrieb. Danach habe ich noch etcd Mitglied für Mitglied auf Version 3.7.3 gehoben, ebenfalls ohne Unterbrechung. Warum, steht weiter unten bei der Sicherheit.

Wichtig dabei: Die KI hat das Update vorbereitet und mich Schritt für Schritt begleitet, aber am Live-Server saß ich selbst. Ich habe jeden Befehl selbst ausgeführt, die Ausgaben geprüft, die Koop-Runde auf den beiden Handys und die Solo-Runde im Browser selbst gespielt und erst nach dem Kanarienvogel das „ja“ für die übrigen Server gegeben. Getestet und abgenommen hat das Update also ein Mensch.

Tada.

Die Datenbank im Cluster

Die Datenbank ist PostgreSQL 18. Am Anfang war es SQLite. Beim Umstieg habe ich die Rangliste neu beginnen lassen, in einer Open Beta geht das.

Inzwischen läuft PostgreSQL ebenfalls als Cluster:

  • Patroni steuert zwei PostgreSQL-Instanzen: einen Leader und einen synchronen Standby. Ein COMMIT kommt erst zurück, wenn der Standby ihn hat. Beim Umschalten geht also nichts verloren.
  • etcd mit drei Mitgliedern hält fest, wer Leader ist. Mehrheitsprinzip, also nie zwei Leader gleichzeitig. Anmeldung und TLS sind an, und die Spiel-Server dürfen in etcd nur ihren eigenen Bereich lesen.
  • Das Spiel kennt beide Datenbank-Server und landet immer beim Leader. Während des Umschaltens wartet jeder Zugriff bis zu 30 Sekunden und wiederholt sich dann.

Umschalten im Live-Test

Getestet habe ich das live:

TestErgebnis
geplantes Umschaltenneuer Leader nach etwa 3 s, Spiel wieder verbunden nach 1,8 s
Absturz des Leaders (docker kill)neuer Leader nach etwa 20 s, alle 12 Sessions aus dieser Zeit stehen danach in der Datenbank
Umschalten unter Last (475 Verbindungen)0 Fehler, Spiel nach gut 2 s wieder verbunden

In allen Fällen blieben die Spieler verbunden und bekamen weiter 60 Spielstände pro Sekunde. Die Datenbank läuft nämlich nur im Hintergrund, nie im Spieltakt.

Einmal hat es trotzdem kurz geruckelt: Beim Umschalten unter Last stand der Spieltakt etwa 100 ms. Lokal ließ sich das nicht nachstellen. Der wahrscheinlichste Grund war das Log: Jede Zeile wurde sofort auf die Platte geschrieben, unter einer Sperre, die auch die Datenbank-Threads hielten. Seitdem schreibt ein eigener Thread das Log.

Sicherung und Zurückspielen

Dazu kommt eine tägliche Sicherung mit pg_dump, sieben Tage lang. Und das Zurückspielen habe ich nicht nur dokumentiert, sondern wirklich ausprobiert. Ich finde ja, ein Backup, das man nie zurückgespielt hat, ist kein Backup.

Für die Probe habe ich eine Testdatenbank mit einer Million Runden gefüllt:

Ergebnis
Größe der Datenbank1,06 GB
Sicherung (gzip)25,8 s, 92,8 MB
ganze Datenbank zurückspielen30 s, das Spiel war davon 1,5 s nicht erreichbar
nur einen Tag zurückholen57 s, das Spiel lief weiter

Während der Sicherung wird nur die Rangliste etwas langsamer. Das Spiel selbst merkt nichts, und die Sicherung läuft ohnehin nachts.

One Button Jump als App auf dem Handy

Die App ist mit Godot gebaut und spielt auf demselben Server wie der Browser. Browser und App können also zusammen in einer Session spielen.

Am PC habe ich App und Browser Anfang Oktober verglichen (gleicher Server, 1280×800, 60 Hz, automatischer Spieler):

Browser (Chrome)App (Godot)
CPU~32 % eines Kerns~14,5 % eines Kerns
Arbeitsspeicher~625 MB (9 Prozesse)~200 MB (1 Prozess)
Antwort auf Eingaben~12 ms~12 ms

Auf dem Handy war der Levelwechsel ein Problem. Auf meinem Testhandy (Galaxy S21 FE) hat der Aufbau eines großen Levels mit fast 15 000 Kacheln 97 bis 109 ms gedauert, in einem einzigen Bild. Das sieht man. Nach dem Umbau (ein Durchlauf über das Raster mit Tabellen statt Nachschlagen pro Kachel) sind es 4 bis 6 ms.

Das zweite Problem war schwankender Mobilfunk. Kommt ein Paket zu spät, steht die Figur kurz und springt dann weiter. Nachgestellt habe ich das mit tc netem (schlechtes LTE: 60 ± 80 ms Verzögerung, 1 % Verlust) und der echten App. Die Lösung: ein etwas größerer Puffer (höchstens 0,25 statt 0,15 s) und bis zu 0,1 s Weiterrechnen, wenn gerade nichts ankommt. Danach gab es in dem Szenario 5 sichtbare Sprünge statt 80. Bei guter Verbindung bleibt der Puffer bei 35 bis 40 ms.

Ähnlich bei der ersten Pause und dem ersten Game over je Runde: Das erste Bild brauchte etwa 20 ms, spätere nur etwa 9 ms, weil Godot jedes Schriftzeichen erst beim ersten Zeichnen rastert. Jetzt werden die Zeichen vorher nach und nach vorgewärmt.

One Button Jump – Die App auf einem Android-Handy hochkant: oben das Spiel mit der springenden Figur, darunter der eigene große rote Button „← Links“
Auf dem Handy

Tests und Sicherheit

Ohne Tests würde ich der KI so einen Umbau nicht überlassen. Das läuft bei jeder Änderung, je nachdem, was geändert wurde:

WasWie vieleWas geprüft wird
Unit-Tests Spielkern169 TestfällePhysik, Level-Generator, Schuldfrage, mit Bots, die Level wirklich durchspielen
Unit-Tests Serverrund 310 TestfälleRäume, Sessions, Rettung, Cluster, Konfiguration; ein Teil gegen echte PostgreSQL
dieselben Unit-Tests mit AddressSanitizeralleSpeicherfehler und undefiniertes Verhalten
Golden Files9 953 FälleLevel, Physik und Zufall Byte für Byte wie die Referenz
Black-Box-Testsrund 285 Tests in 31 Dateiendas fertige Docker-Image über HTTP, WebSocket und Logs, mit PostgreSQL
Mischtest alter/neuer Server7 Testsaltes und neues Image an derselben Datenbank: Migration, Rolling Update, gerettete Runde läuft im neuen weiter
Lasttest unter AddressSanitizer60 s LastWettläufe und Speicherfehler unter Last
Browser-Check mit Playwrightein DurchlaufLobby, Solo-Runde, Zuschauen, Übersicht, Anleitung
App-Prüfungen in Godot22 Prüfskripte, 19 gemeinsame Client-FälleSeiten, Steuerfeld, Steam, Steam Deck, schmale Handys, Netz-Code wie im Browser
App-Buildsalle PlattformenWindows, Linux, Steam und Android; die Linux-Builds spielen 5 s gegen den Server

Anfangs dauerte ein kompletter Lauf etwa 45 Minuten. Viel davon war reines Warten, zum Beispiel feste Wartezeiten bei den Bildschirm-Prüfungen der App. Nach dem Aufräumen (Schritte parallel, Compiler-Cache, Black-Box-Tests in mehreren Prozessen) sind es gut 10 Minuten. Eine reine Browser-Änderung ist in etwa einer Minute geprüft. Ein Profil legt fest, was bei welcher Änderung laufen muss.

Die wöchentliche Sicherheitsprüfung

Dazu kommt etwa einmal pro Woche eine Sicherheitsprüfung:

  • SBOM aller ausgelieferten Bibliotheken und Abgleich mit der OSV-Datenbank,
  • Trivy für die Docker-Images,
  • clang-tidy und cppcheck für den eigenen C++-Code,
  • libFuzzer mit 16 Zielen, von den Eingabe-Parsern bis zu ganzen Sessions,
  • OWASP ZAP gegen das Spiel hinter genau der nginx-Konfiguration vom Live-Server, während Chromium eine Runde spielt.

Gefunden hat das zum Beispiel eine als hoch eingestufte Lücke im etcd-Image, in der mitgelieferten Kompressions-Bibliothek. Also etcd auf eine neuere Version gehoben.

Und sogar kurz vor dem Release 0.44 hat die Prüfung noch eine Lücke gefunden. Wieder im etcd-Image, schon zum zweiten Mal, und wieder als hoch eingestuft. Diesmal steckte sie in der Zertifikatsprüfung von Go, mit dem etcd gebaut ist. Behoben ist sie mit etcd 3.7.3, und das läuft seit dem Release 0.44 auch live.

Was gut lief, was schwierig war

Gut lief vor allem das Tempo: Seit dem Umbau auf C++ Anfang Oktober sind über 30 Versionen erschienen, und jede neue Funktion gibt es gleich in Browser und App und in allen Sprachen. Und für fast jede Leistungsfrage gibt es inzwischen ein Werkzeug, das sie misst, statt zu raten.

Schwierig war vor allem „fühlt sich nicht flüssig an“. Das findet die KI nicht von allein. Ich muss es ausprobieren, genau beschreiben und dann messen lassen. Beim Cluster steckt viel Kleinkram in Rettung, Kennzeichen und Versionen, und einige Fehler kamen erst bei der Generalprobe raus. Und ab Version 1.0 dürfen keine Spielerdaten mehr verloren gehen. Darauf muss jedes Update achten.

Nur weil die KI den Code schreibt, bin ich also nicht fertig. Ich muss trotzdem wissen, was ich will. Und ich muss es ausprobieren. Sehr oft.

One Button Jump – Seite „Was ist neu?“ mit den Versionen 0.44, 0.43 und 0.42 und ihren Änderungen, oben die neueste Version 0.44 „Neue Seite: Neuigkeiten“
Über 30 Versionen seit Oktober

Wie weit One Button Jump ist

One Button Jump ist in der Open Beta. Es wird noch weiterentwickelt, es kann also Fehler geben, und manches ändert sich noch.

Stand heute (Version 0.44):

  • Browser: Spielbar unter onebuttonjump.de.
  • Android: Die App läuft gerade im internen Test bei Google Play. Öffentlich ist sie noch nicht.
  • PC über Steam (Windows, Linux, Steam Deck): In Vorbereitung, noch nicht veröffentlicht.

Was als Nächstes kommt, steht seit Version 0.44 auf der Seite „Neuigkeiten“, im Browser und in der App.

One Button Jump – Seite „Neuigkeiten“ mit dem Beitrag „Version 0.44 ist da: Neuigkeiten direkt vom Team“
Neuigkeiten aus der Entwicklung

One Button Jump mitspielen

Wer Lust hat: onebuttonjump.de aufrufen, „▶ Jetzt spielen“ klicken und Freunde einladen.

Über Feedback freue ich mich sehr. Was nervt, was fehlt, wo hängt es? Einfach hier in die Kommentare schreiben.

Und falls jemand Lust auf einen Lasttest mit echten Menschen hat: Platz ist genug.

Ach ja, eins noch. Passend zum Thema ist auch dieser Artikel mit KI entstanden. Claude Code hat aus meinen Projektunterlagen, Tickets und Messungen den Entwurf geschrieben. Ich habe ihn gelesen, ergänzt und entschieden, was reinkommt. Die Zahlen sind also echt, nur abgetippt habe ich sie nicht selbst. Genau wie beim Spiel.

Mal schauen, was dabei rauskommt.