
One Button Jump – ein Jump & Run mit C++-Server, Cluster und ganz viel Vibe Coding
Dieser Beitrag über One Button Jump wurde mit KI (Claude Code) erstellt und von mir gelesen, ergänzt und freigegeben.
Inhalt
- Die Idee hinter One Button Jump
- So spielt man
- One Button Jump und Vibe Coding
- Die Technik hinter One Button Jump
- Der Server rechnet alles
- Von Python nach C++
- Wie viel hält ein Server aus?
- Mehrere Spiel-Server
- Die Datenbank im Cluster
- One Button Jump als App auf dem Handy
- Tests und Sicherheit
- Was gut lief, was schwierig war
- Wie weit One Button Jump ist
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.

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.

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“.

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 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
| Teil | Technik |
|---|---|
| Spiel-Server | C++20, uWebSockets, eigene Physik |
| Browser | JavaScript mit Phaser 3, ohne Build-Schritt |
| App | Godot 4 (Android, Windows, Linux, Steam) |
| Datenbank | PostgreSQL 18 mit Patroni und etcd |
| Davor | nginx |
| Betrieb | Docker 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.

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:
| Format | Daten pro Minute |
|---|---|
| JSON roh | 496 KiB (100 %) |
| msgpack | 364 KiB (73 %) |
| JSON + deflate (damals im Browser) | 96 KiB (19 %) |
| JSON + deflate mit größerem Fenster | 53–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:
| Was | Wie viele |
|---|---|
| komplett erzeugte Level | 5 620 |
| nachgespielte Runden (Physik-Schritte) | 250 (272 922 Schritte, 1 334 Tode) |
| Fälle zum Level-Charakter | 3 600 |
| Fälle zur Schwierigkeit | 375 |
| Fälle zum Zufallsgenerator | 78 |
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.Randomvon 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.
| Verbindungen | Sessions | Spieltakt p99 | RAM Spiel |
|---|---|---|---|
| 200 | 64 | 10 ms | 39 MiB |
| 300 | 100 | 13 ms | 44 MiB |
| 400 | 133 | 15 ms | 52 MiB |
| 510 | 175 | 22 ms | 62,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 %.

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.

Server wechseln, ohne dass Runden abbrechen
Das ist der Teil, auf den ich am meisten stolz bin.
Ein Spiel-Server kann sich abmelden (Drain):
- Er nimmt keine neuen Sessions mehr an.
- Laufende Runden dürfen noch eine Weile weiterlaufen.
- Danach hält er die übrigen Runden am nächsten Checkpoint oder am Levelende an und speichert sie in der Datenbank („gerettet“).
- Er gibt seine Kennzeichen samt den geretteten Runden an einen anderen Server ab.
- 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:
| Test | Ergebnis |
|---|---|
| geplantes Umschalten | neuer 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 Datenbank | 1,06 GB |
| Sicherung (gzip) | 25,8 s, 92,8 MB |
| ganze Datenbank zurückspielen | 30 s, das Spiel war davon 1,5 s nicht erreichbar |
| nur einen Tag zurückholen | 57 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.

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:
| Was | Wie viele | Was geprüft wird |
|---|---|---|
| Unit-Tests Spielkern | 169 Testfälle | Physik, Level-Generator, Schuldfrage, mit Bots, die Level wirklich durchspielen |
| Unit-Tests Server | rund 310 Testfälle | Räume, Sessions, Rettung, Cluster, Konfiguration; ein Teil gegen echte PostgreSQL |
| dieselben Unit-Tests mit AddressSanitizer | alle | Speicherfehler und undefiniertes Verhalten |
| Golden Files | 9 953 Fälle | Level, Physik und Zufall Byte für Byte wie die Referenz |
| Black-Box-Tests | rund 285 Tests in 31 Dateien | das fertige Docker-Image über HTTP, WebSocket und Logs, mit PostgreSQL |
| Mischtest alter/neuer Server | 7 Tests | altes und neues Image an derselben Datenbank: Migration, Rolling Update, gerettete Runde läuft im neuen weiter |
| Lasttest unter AddressSanitizer | 60 s Last | Wettläufe und Speicherfehler unter Last |
| Browser-Check mit Playwright | ein Durchlauf | Lobby, Solo-Runde, Zuschauen, Übersicht, Anleitung |
| App-Prüfungen in Godot | 22 Prüfskripte, 19 gemeinsame Client-Fälle | Seiten, Steuerfeld, Steam, Steam Deck, schmale Handys, Netz-Code wie im Browser |
| App-Builds | alle Plattformen | Windows, 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.

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 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.