// TECH //
Wie Clash of Stars Hunderte Schiffe synchron hält
Zwei Browser, ein Match, und keine einzige Schiffsposition auf der Leitung. Jede Zahl unten wurde aus dem Build abgelesen, der gerade auf BlitzBrawl live ist.
Niemand hat das Sagen
Die meisten Online-Spiele laufen über einen autoritativen Server: Eine Maschine besitzt die Wahrheit, und jeder Spieler fragt sie, was passiert ist. Clash of Stars hat keine solche Maschine. Der einzige Server in der Spielschleife ist ein Relay, das ein Paket von einem Spieler nimmt und an die anderen weiterschiebt, und die Nutzlast nie öffnet. Für dieses Relay sehen ein Flottenbefehl und ein Keep-alive gleich aus.
Das Match existiert also an zwei, drei oder vier Orten gleichzeitig, eine Kopie pro Browser, und keine Master-Kopie korrigiert sie. Sie stimmen überein, weil jede aus derselben Eingabe dasselbe berechnet, und weil die Engine daran arbeitet, zu beweisen, dass das noch stimmt.
Zwei Browser, eine Uhr
Die Simulation läuft mit festen 20 Ticks pro Sekunde, ein Tick ist also genau 50 ms Spielzeit. Ticks hängen nicht an deiner Bildrate: Tick k ist zu einem festen Abstand vom Startmoment des Matches fällig, und in jedem Frame simuliert die Engine jeden Tick, der fällig geworden ist. Stockt dein Gerät, ist das Aufholen auf 250 ms pro Frame gedeckelt, und der Überschuss wird verworfen statt nachgeholt, sodass ein langsames Handy Spielzeit verliert, statt für immer zurückzufallen.
Zwei Geräte, deren Uhren nicht übereinstimmen, laufen im selben Moment auf verschiedenen Ticknummern, was jeden eingehenden Befehl verspätet aussehen lässt. Während des 3-2-1-Countdowns pingt jeder Client seine Mitspieler alle 120 ms, korrigiert die Umlaufzeit und schiebt seinen Startmoment auf den Median der Gruppe, während der Tickzähler auf null festgehalten wird, sodass nichts springt. Danach stupst er sein Tickintervall um 2 Prozent an, oder um 8 Prozent, wenn er weit daneben liegt.
Was wirklich unterwegs ist
Schiffspositionen gehen nie über die Leitung. Befehle schon. Ein Senden ist ein binäres Paket: ein Opcode-Byte, dein Platz, eine Sequenznummer, der Tick, auf dem es laufen soll, der Zielplanet, der Prozentsatz, und die Quellplaneten mit je einem Byte. Von einem Planeten zu senden sind 12 Bytes. Alle 28 Planeten der größten Karte auszuwählen und von jedem zu feuern sind 39 Bytes, und dieses eine Paket kann mehr als tausend Schiffe in die Luft bringen.
Sieben weitere Opcodes decken den Rest ab: zwei Arten, einen Schwarm im Flug umzulenken, einer, der eine Aufgabe oder einen Platzwechsel trägt, und vier für Ping, Pong, Bestätigung und den Zustands-Hash. Befehle werden alle 250 ms erneut gesendet, bis jeder lebende Mitspieler sie bestätigt hat, und Empfänger verwerfen Wiederholungen nach Absender und Sequenznummer, sodass ein Duplikat nichts kostet und ein verlorenes Paket einen erneuten Versuch.
Deine eigenen Befehle warten eine Viertelsekunde
Den Tick, auf dem ein Befehl läuft, stempelt der Absender, 5 Ticks in die Zukunft, also 250 ms, und er fährt im Paket mit. Empfänger rechnen ihn nie neu: Ein Befehl läuft überall auf genau diesem Tick, oder er läuft nirgends rechtzeitig. Die Verzögerung ist fest statt auf deinen Ping abgestimmt, damit ein Befehl bei allen auf demselben Tick landet. Der Solo-Modus nutzt null Verzögerung über denselben Codepfad: Einzelspieler ist Multiplayer ohne den Transport.
Kommt ein Befehl zu spät, spult das Match zurück
Alle 4 Ticks speichert die Engine eine vollständige Kopie des Spielzustands und behält die letzten 64, das sind 256 Ticks, rund 12,8 Sekunden zurückspulbare Geschichte. Ein Befehl, dessen Ausführungstick schon in der Vergangenheit liegt, wird nie verworfen. Die Engine legt ihn ins Befehlsprotokoll, stellt die neueste Kopie bei oder vor diesem Tick wieder her und simuliert vorwärts, wobei sie jeden protokollierten Befehl in einer globalen Reihenfolge nachspielt: Ausführungstick, dann Platz, dann Sequenznummer.
Nachspielen läuft stumm und mit unterdrückten Spielereignissen, damit eine Eroberung, die du schon gesehen hast, sich nicht zweimal meldet. Der wichtigste Test in der Test-Suite dieses Spiels stellt sicher, dass Zurückspulen und Nachspielen auf einem Zustand landen, der bitgenau dem Lauf entspricht, der nie zurückgespult hat.
Hunderte Schiffe, jedes auf jeder Maschine berechnet
Es gibt hier kein Flottenobjekt. Jedes Schiff ist eine eigene Entität mit eigener Position, und die Form des Schwarms ergibt sich aus drei Regeln, die einmal pro Tick laufen: den Mittelpunkt des Zielplaneten ansteuern, überlappende Paare auseinanderschieben, um jeden Planeten herumgleiten, der nicht das Ziel ist. Weil die Form aus der Simulation entsteht und nicht aus dem Renderer, zeichnet jeder Client den identischen Schwarm, ohne ein Byte dafür auszugeben.
Ein Match mit vier Spielern hat typischerweise zwischen zwei- und fünfhundert Schiffe gleichzeitig in der Luft, und wer eine große Karte eine Minute lang größtenteils hält, kann mit einem Senden über 1.600 auf einmal starten. Die Simulation setzt keine Obergrenze; der Renderer hört bei 4.096 Schiffen pro Frame auf zu zeichnen, und der Performance-Check des Spiels nimmt etwa 1.170 als schlimmsten Fall.
Das reproduziert sich nur unter harten Regeln: keine Zufallszahlen, die die Engine nicht selbst seedet und im Zustand mitführt, nirgends Trigonometrie, jede Liste in aufsteigender ID-Reihenfolge durchlaufen.
Eine 32-Bit-Zahl, die eine Lüge erwischt
Alle 40 Ticks, also alle 2 Sekunden, faltet jeder Client seinen gesamten Simulationszustand in einen 32-Bit-Hash und sendet ihn: Planetenbesitzer und Garnisonen, von jedem Schiff ID, Besitzer, Ziel und das exakte Bitmuster seiner Koordinaten, die Spieler-Flags, den Zustand des Zufallsgenerators. Rohe Float-Bits statt gerundeter Werte zu falten heißt, dass eine Abweichung in der letzten Nachkommastelle erwischt statt glattgebügelt wird. Die Prüfung kostet 10 Bytes.
Abweichungen werden mit Misstrauen behandelt, weil die meisten nicht echt sind. Ein Zurückspulen überschreibt Hashes, die ein Mitspieler schon gesendet hat, also werden korrigierte Werte in drei Runden erneut gesendet, Empfänger behalten den neuesten Wert pro Tick, und ein Vergleich wartet 8 Ticks, bevor er zählt. Einer Abweichung wird erst geglaubt, wenn sie zwei Auswertungen im Abstand von 24 Ticks übersteht. Ein wirklich identisches Match darf nie eine Abweichung melden, und auch das ist ein Test.
Wenn ein Client wirklich abgedriftet ist
Eine bestätigte Abweichung löst eine Angleichung aus, keinen Streit. Jeder betroffene Client außer dem lebenden Spieler mit der niedrigsten Nummer bittet um einen Resync; dieser Spieler antwortet mit einem serialisierten Snapshot plus dem Befehlsrest zum Aufholen. Der Anfragende übernimmt ihn, setzt seine Sequenznummern oberhalb der Hochwassermarke fort und spult vor. Ist der Ausreißer der niedrigste Platz, übernehmen trotzdem alle seinen Zustand: Angleichung schlägt Korrektheit, denn das Match ist so oder so wieder identisch.
Ein Snapshot, den niemand angefordert hat, wird ignoriert, und jede Nutzlast wird zuerst auf ihre Form geprüft. Dieselbe Warteschlange trägt die alltäglichen Fälle: Ein Spieler, der acht Sekunden lang still ist, wird mit einem geordneten Befehl an die Bot-KI übergeben, sodass jede Maschine das im selben Tick tut, und ein zurückkehrender Spieler zieht sich einen Snapshot und holt sich den Platz zurück.
Nichts davon braucht einen Server, der das Spiel versteht. Es braucht eine gemeinsame Uhr, eine strenge Reihenfolge für Befehle, einen billigen Weg zum Zurückspulen, und eine kleine Zahl, die eine Maschine erwischt, die aus dem Tritt gerät.
▶ Clash of Stars spielen