xkcds Entwicklungsnotizen zu „Machine“
Erste Konzeption
- Die Idee wurde bis Ende März erwogen und Anfang April festgelegt.
- „Könnte man wie bei den riesigen gekachelten Vorrichtungen aus den Blue-Ball-GIFs der Something-Awful-Nutzer eine gigantische, kachelbasierte Maschine bauen? Jeder steuert ein kleines Rechteck bei.“
- Anfangs fühlte es sich an, als sei die Idee vollständig ausgereift, doch in den tatsächlichen Gesprächen wurde klar, dass noch viele Entscheidungen getroffen werden mussten.
- Bei zentralen Punkten hatten alle unterschiedliche Vorstellungen: Woher kommen die Kugeln, sehen alle dieselbe Maschine, was ist das Ziel, wie interagieren die Spieler usw.?
Erkenntnisse aus früheren Versuchen
- Es gab bereits Erfahrung mit interaktiven Comics, die auf nutzergenerierten Inhalten basieren.
- Lorenz: ein Exquisite-Corpse-Projekt, bei dem Leser Panel-Texte schreiben, um Witze und Handlung weiterzuentwickeln (hat sehr viel Spaß gemacht)
- Collector's Edition: ein Spiel, bei dem Leser in den xkcd-Archiven versteckte Sticker finden und dauerhaft auf eine gemeinsame Leinwand kleben (führte nicht zum gewünschten Ergebnis)
- Wenn man anfangs mit einer leeren zentralen Karte beginnt, endet es im Chaos.
- Es fehlten Anreize für die Platzierung der Sticker, sodass individuelles Handeln den Plot kaum voranbringen konnte und nur einfache Muster entstanden.
- Es gab keine übergreifende Geschichte oder Zielsetzung, und auch die Beziehungen zwischen den Stickern waren unklar.
- Damit eine kollektive Leinwand funktioniert, muss man anhand von Beispielen zeigen, was man bauen kann, und es braucht einen geteilten Kontext und ein gemeinsames Ziel.
Entwurf der Randbedingungen
- Nachdem entschieden war, eine große Kugelfall-Maschine zu bauen, stand man vor zu vielen Wahlmöglichkeiten.
- Es wurde beschlossen, ein Raster von 100x100 zu verwenden.
- 10.000 Kacheln in Echtzeit auf dem Client zu simulieren, wirkte riskant.
- Es war unklar, wie Spieler ohne direkte Kommunikation Teilbereiche einer komplexen Maschine bauen sollen und ob getrennte Kacheln beim Zusammenfügen überhaupt funktionieren würden.
- Nach mehreren Gedankenspielen wurden drei Kernprinzipien festgelegt:
1. Maximale Ausdrucksfreiheit für die Spieler, selbst auf Kosten der Genauigkeit
- Wie vorhersehbar muss die Maschine sein?
- Es wurde auch erwogen, alles auf dem Server auszuführen oder einzelne Kacheln zu validieren, aber im Prototyp-Editor zeigte sich, dass sich chaotische Kugel-Kollisionsmuster leicht erzeugen lassen.
- Sobald Kugeln sich nicht ungehindert geradlinig bewegen, lassen sich leicht unvorhersehbare Maschinen bauen.
- Eine höhere Vorhersagbarkeit der Maschine steht im Konflikt mit der Freiheit der Spieler.
- Auch der knappe Entwicklungszeitraum sprach für einen Ansatz mit weniger Vorhersage und Simulation.
- Daher wurde entschieden, den Spielern sehr flexible Gestaltungsmöglichkeiten zu geben, einschließlich extrem nichtdeterministischer oder kaputter Maschinen.
- Dafür waren aktive Moderation nötig, um die Einhaltung der Randbedingungen zu prüfen und unangebrachte Inhalte zu entfernen.
2. Strenge Randbedingungen bereitstellen, die kompatible und austauschbare Maschinen fördern
- Die Kombination aus Moderation und unvorhersehbaren Spielermaschinen machte paradoxerweise noch mehr Ordnung erforderlich.
- Anfangs wurden Ein- und Ausgänge als völlig freies Format betrachtet, doch im Moderationsprozess wurde klar, dass der Austausch früher Kacheln massive Ausfälle verursachen könnte.
- Deshalb wurden Randbedingungen entworfen, die stark genug sind, damit mehrere Spieler im selben Kachelraum kompatible Designs bauen können.
- Es wurde das Robustness Principle angewandt: „Sei konservativ bei den Daten, die du sendest, und tolerant bei den Daten, die du empfängst.“
- Um Ein-/Ausgabe-Randbedingungen zu definieren, war von Anfang an eine vollständige Maschinenkarte nötig.
- Über die Kartenerzeugung ließ sich auch die Schwierigkeit der Maschine steuern, von einfachen 1-Eingang-1-Ausgang-Fällen bis zu komplexen 4-Eingang-4-Ausgang-Zusammenführungen.
- Für Echtzeit-Feedback wurde festgelegt, dass eine Kachel Kugeln ungefähr in dem Tempo ausgeben muss, in dem sie ähnliche Mengen empfängt.
- Maschinen, die Kugeln verschlucken oder verzögern, wurden eingeschränkt.
- Kacheln wurden mit zufälligen Eingabegeschwindigkeiten auf Chaos getestet.
- Daraus entstand das Prinzip: „Lass die Maschine eine Weile laufen und prüfe, ob sie die Randbedingungen im Durchschnitt erfüllt.“
3. Die Maschine muss innerhalb der ersten 30 Sekunden einen stabilen Zustand erreichen
- Es stellte sich die Frage, wie lange Moderatoren zuschauen müssen.
- Die dafür nötige Zeit wurde für die gesamte Maschine hochgerechnet (83,3 Stunden bei 10.000 Kacheln).
- Daher wurde willkürlich entschieden, dass sie innerhalb von 30 Sekunden in einen stabilen Zustand übergehen muss.
- Es wurde festgelegt, dass Kugeln nach 30 Sekunden verschwinden.
- Anfangs gab es keine Ablaufzeit, sodass sich während der Lernphase der Spieler Kugeln ansammelten und den Bildschirm füllten.
- Mit der steigenden Zahl aktiver Rigid Bodies wurde auch die Physiksimulation langsamer.
- Die Kugeln wurden eher zum Hindernis als zum Spaßfaktor.
- Durch das Ablaufverhalten akkumuliert die Maschine mit der Zeit keine Fehler.
- Ein Moderator muss nur 30 Sekunden zusehen, um grob zu verstehen, wohin die meisten Kugeln gelangen können.
Simulation und Surrealität
- Die Machine-Architektur hatte zwei große Herausforderungen:
- Würde es mit den obigen Design-Randbedingungen überhaupt funktionieren, heterogene Kacheln zu einer gesamten Maschine zu verbinden?
- Das wurde verifiziert, indem einige kleine Karten erzeugt und gelöst wurden.
- Wenn sich die riesige Maschine weder auf Server noch Client in Echtzeit ausführen lässt, wie soll man sie dann darstellen?
Ziel war es, Scrollen zu ermöglichen und dabei einer einzelnen Kugel zu folgen
- Auch wenn nicht die gesamte Maschine simuliert wird, muss der Bereich um den sichtbaren Ausschnitt für die Spieler simuliert werden.
- Anfangs wurde getestet, auf einer unendlichen Karte nur den sichtbaren Bereich zu simulieren.
- Das funktionierte ziemlich gut, aber beim Scrollen traten Kacheln mit leerem Anfangszustand in die Simulation ein, wodurch Lücken im Fluss entstanden.
- Statt leerer Kacheln musste es so aussehen, als sei dort bereits Aktivität vorhanden.
Zweite Herausforderung: Snapshots von Kacheln nur nach Erreichen des stabilen Zustands aufnehmen, sodass sie erst kurz vor dem Sichtbarwerden durch Scrollen existieren
- In der finalen Comic-Version mit deaktiviertem Display-Clipping (
CSS overflow:hidden, contain:paint deaktiviert):
- Hast du die Snapshots bemerkt? Ohne besondere Aufmerksamkeit sind sie schwer zu erkennen.
- Nur gerenderte Kacheln existieren in der Physiksimulation.
- Anzeigeoptimierung: Sichtbar sind nur Kugeln innerhalb des Viewports, simuliert wird aber über den gesamten Kachelbereich.
- Um die Oberseite der Maschine vorzutäuschen, werden in der obersten simulierten Zeile Kugeln erzeugt und eingespeist, basierend auf der erwarteten Rate aus den Eingabe-Randbedingungen.
- Die Erzeugung der Snapshots wurde in das Moderations-UI integriert.
- Moderatoren mussten vor der Freigabe einer Kachel mindestens 30 Sekunden warten.
- Beim Klick auf die Freigabe-Schaltfläche wird ein Snapshot erzeugt.
- Nach Ermessen des Moderators kann auch etwas länger gewartet werden, bis die Maschine gut aussieht.
- Die Snapshot-Methode funktionierte besser als erwartet.
- Ein positiver Nebeneffekt war das Zurücksetzen akkumulierte Fehler in der Maschine.
- Der erste Eindruck einer Kachel beim Scrollen ist ein sauberer, ansprechender Zustand, den ein Moderator gut fand.
- Tatsächlich können viele Maschinen bei längerer Beobachtung kaputtgehen oder zerfallen, aber das fällt nicht auf, weil man beim Weitererkunden in neue Snapshots übergeht.
- Die im Comic scrollende Maschine ist nicht real. Sie ist surreal.
- Das Ganze wird nie auf einmal simuliert, führt aber gerade dadurch zu einem besseren Ergebnis.
Tausende Kugeln mit React und dem DOM rendern
- Die Umsetzung basiert auf der Rapier-Physik-Engine.
- Beeindruckende Performance dank hervorragender Dokumentation, einer sauberen API mit nützlichen Grundbausteinen und einer Rust-Implementierung, die im Browser als WASM läuft.
- Anfangs war die garantierte Deterministik von Rapier besonders attraktiv, doch eine serverseitige Simulation wurde nicht verwendet.
- Auf Rapier wurde ein benutzerdefinierter React-Kontext
<PhysicsContext> aufgebaut.
- Er erstellt Rapier-Physikobjekte und verwaltet sie innerhalb des React-Komponenten-Lifecycles.
- Das erleichterte die Entwicklung von „Widget“-Komponenten für jedes platzierbare Objekt mit Physik oder Kollisionsflächen.
- React diente als einfacher, pragmatischer Scene Graph.
- Das Laden und Entladen von Kacheln beim Scrollen der Ansicht wurde vereinfacht: Beim Unmounten einer Kachel werden sämtliche Physik- und DOM-Elemente bereinigt.
- Als Bonus ließ sich Hot Reloading leicht mit Fast Refresh verbinden, was beim Feinjustieren von Kollisionsformen wirklich hilfreich war.
- Ein weiterer Vorteil des React-Kontext-Ansatzes:
- Physik-Hooks verhalten sich als No-op, wenn sie sich nicht innerhalb von
<PhysicsContext> befinden.
- Das wurde im Moderations-UI für das Rendern statischer Kachel-Vorschauen genutzt.
- Rückblickend wäre es besser gewesen, für die Erzeugung von Rapier-Objekten Komponenten statt Hooks zu verwenden (wie es
react-three-rapier macht).
- Das passt besser zum React-Diffing, weil
useEffect bei Abhängigkeitsänderungen die vorherige Instanz entfernt und neu erzeugt.
- Machine wird vollständig mit dem DOM gerendert.
- Während der frühen Entwicklung gab es die Sorge, bei der Performance an die Grenzen des DOM-Renderings zu stoßen.
- Falls es zu langsam geworden wäre, wäre wohl auf PixiJS oder canvas umgestellt worden, aber zunächst wollte man sehen, wie weit sich das DOM ausreizen lässt.
- Optimierung der Render-Performance:
- Die Frame-Loop wendet Styles direkt auf Widgets mit Physiksimulation an.
- Das React-Diffing läuft nur bei strukturellen Änderungen im Scene Graph.
- Anfangs wurden die Kugeln mit React gerendert.
1 Kommentare
Hacker-News-Kommentare
Fasst man die verschiedenen Kommentare zusammen, ergibt sich ungefähr Folgendes:
Rapierverwendet, wobei es durch rekursive Fehler auch zu Abstürzen kam