Chrono in Unreal, Teil 3: Die Fallen
Teil 2 hat die Architektur beschrieben. Dieser Teil ist die Liste der Dinge, die trotzdem schiefgingen, sortiert nach Thema, nicht nach Zeit. Jede Falle hat eine Ursache, die man verstehen kann, und die meisten hätten sich mit dem Wissen von jetzt vermeiden lassen. Das ist der Zweck der Liste.
Die Demo, um die es in der Serie geht. Auf YouTube in 1080p ist die Konsole lesbar.
Die Räder
Beim Schwimmen kippte das linke Vorderrad plötzlich auf 50 Grad Vorspur. Im Chrono® Zustand genauso wie im Bild, also kein Darstellungsproblem. Es waren zwei Ursachen übereinander.
Die erste: der Schaufelradschub der drehenden Räder griff entlang der X Achse der Radspindel an. Die Spindel dreht sich mit dem Rad, der Schub lief also einmal pro Umdrehung im Kreis und hebelte den Achsschenkel über den Totpunkt der Spurstange. Ein Vorzeichen oder eine Achse falsch gewählt, und die Kraft ist keine Kraft mehr, sondern ein Rührwerk. Jetzt ist die Schubrichtung das Kreuzprodukt aus Achsrichtung und Hochachse, unabhängig von der Raddrehung.
Die zweite: das Chrono Fahrzeug hat keinen Zuganschlag in der Federung und keinen Lenkanschlag am Achsschenkel, weil das Modell für Fahrten auf dem Boden gedacht ist, wo beides nie erreicht wird. Ein entlastetes Rad im Wasser hängt voll aus, und in dieser Stellung reicht eine kleine Störung, um die Lenkung durchschlagen zu lassen. Beides ist nachgerüstet: ein Zuganschlag als begrenzte Federkraft ab 41 cm Ausfederung, ein Lenkanschlag als begrenztes Drehmoment um den Achsschenkelbolzen ab 45 Grad, mit Dämpfung. Der Anschlag musste einmal von 35 auf 45 Grad, weil das kurveninnere Rad bei vollem Einschlag schon 45 Grad erreicht. Ein Anschlag, der die normale Fahrt einschränkt, ist kein Anschlag, sondern ein Fehler.
Und ein Versuch dazwischen, der die wichtigste Lektion des Tages war: ein Wasserwiderstand gegen das Raddrehen, als quadratisches Drehmoment auf die Spindel, liess die Simulation in Sekunden explodieren. Die Spindel ist leicht, das Moment war gross, bei 2 ms Schrittweite ist das ein steifer Term, den der Solver nicht hält. Regel seitdem: eigene Kräfte klein und mit Obergrenze, Drehmomente auf leichte Körper nur gedeckelt, und jede neue Kraft zuerst im Debugstart schwimmend prüfen.
Zuletzt ein Reset Absturz: die Akkumulatoren, über die eigene Kräfte auf Chrono Körper gehen, haben Indizes. Nach dem Neuanlegen des Systems zeigten die alten Indizes auf Körper, die es nicht mehr gab. Indizes zurücksetzen, wo das System zurückgesetzt wird.

Die Automatik
Die Automatik in Chrono schaltet nach der Turbinendrehzahl hinter dem Drehmomentwandler. Auf losem Sand haben die Räder starken Schlupf und graben sich ein. Der Wandler sieht dabei kaum Last, aber hohe Eingangsdrehzahl vom Motor. Die Turbine erreicht den Schaltpunkt nie, der Motor hängt am Begrenzer bei 2570 Umdrehungen mit wenig Moment, das Fahrzeug bleibt stehen und wühlt sich tiefer. Erste Lösung: Getriebe im manuellen Modus, der Pawn schaltet nach Motordrehzahl.
Zweite Falle, umgekehrt: drehen die Räder auf der Düne frei durch, dreht die Turbine mit, und nach Motordrehzahl geschaltet geht es in den zweiten Gang. Dort fehlt die Untersetzung, die Turbine fällt unter den Rückschaltpunkt, und nach 0.8 Sekunden Sperrzeit kommt der erste Gang zurück. Das Log zeigte ein Pendeln zwischen erstem und zweitem Gang alle 0.8 Sekunden. Zwei Regeln haben es beendet: hochschalten nur, wenn die Turbine mindestens 90 Prozent des Schaltpunkts dreht, denn der nächste Gang hat 53 bis 75 Prozent der Übersetzung, und nur, wenn die Fahrgeschwindigkeit zum nächsten Gang passt, 16 km/h für den zweiten, 30 für den dritten. Das ist das Kickdown Verhalten eines realen Automaten.
Dritte: Wählhebelstellung 2. Auf dem Aufstieg begrenzt der Autopilot auf den zweiten Gang, wie ein Fahrer im Gelände. Die erste Version schaltete die Begrenzung nach dem Nickwinkel ein und griff damit auch auf der Sprungrampe, wo das Fahrzeug beschleunigen soll. Jetzt gilt sie nach Position, 25 Meter nach dem Dünenfuss bis kurz vor den Kamm. Eine Regel, die an einer Messgrösse hängt, die auch anderswo vorkommt, greift auch anderswo.
Dritte Falle, erst am zweiten Tag bemerkt: bei der Vollbremsung stand der Motor im HUD auf 75 Umdrehungen, während der Klang im Leerlauf weiterlief. Der Wandler zog den Motor mit den blockierten Rädern herunter, und der Wellenmotor von Chrono hat keinen Leerlaufregler; mit dem Pedal oben dreht er einfach aus. Ein proportionaler Gasanteil unter 900 Umdrehungen, höchstens 30 Prozent, hält den Leerlauf, und seit das ABS die Räder rollen lässt, kommt der Wandler gar nicht mehr in diese Lage.

Trigger und Zeitbasen
Die Demo hat drei Uhren: die Weltzeit der Engine, die Simulationszeit von Chrono, die während des Startcountdowns steht, und das Alter der Drohne, das ebenfalls wartet. Drei Uhren sind zwei zu viel, aber jede hat einen Grund, und jede Falle in diesem Abschnitt kommt daher, dass ein Trigger an der falschen hing oder gar nicht sichtbar war.
- Der Tiefflug der Drohne war als Variante des Orbits gebaut: kleinerer Radius, weniger Höhe, sechs Sekunden lang. Der Radius wird mit einer Zeitkonstante von zwei Sekunden geglättet und kam in sechs Sekunden nie unten an. Das Log sagte “läuft”, das Bild sagte “nichts passiert”. Zweite Version: explizite Wegpunkte, 25 Meter vor das Auto und dicht daneben zurück. Lief in 4.7 Sekunden ab, davon 1.5 sichtbar, wieder sah es niemand. Dritte Version: sechs Sekunden auf einem Meter neben dem Auto herfliegen, dann nach vorn weg. Das war zu sehen. Und dann wurde die Szene ohnehin ans Ende verlegt, als Tiefflug zurück zum Startpunkt mit Halt auf der Anhöhe. Vier Versionen für eine Szene, weil “Trigger feuert” und “Zuschauer sieht es” zwei verschiedene Dinge sind. Seitdem hat jeder Trigger eine Logzeile mit Start und Ende und wird mit Screenshots zu den Zeitpunkten geprüft, nicht mit dem Log allein.
- Der weite Wurf beim Ballspiel löst aus, wenn das Fahrzeug 90 Meter entfernt ist. Der Läufer kreuzt die Fahrlinie rund zwölf Sekunden später. Diese zwölf Sekunden bestimmen, wie schnell das Fahrzeug ankommen darf, damit die Notbremsung aus 55 km/h auf der richtigen Höhe liegt. Choreografie ist Rechnen mit Sekunden.
- Das Eintauchereignis für den Klang ist eine Flanke pro Rad: trocken zu nass, höchstens alle 0.3 Sekunden. Der Schwall auf die Windschutzscheibe hing zuerst an derselben Flanke mit einer Mindesttiefe von 40 cm. Auf der Flanke ist das Rad aber gerade erst 4 cm nass. Die Bedingung war nie wahr. Eine Flanke und ein Pegel sind zwei verschiedene Ereignisse.
- Screenshots zu festen Zeiten sind selbst ein Trigger: das Schreiben eines Bildes kostet 200 bis 400 ms und erscheint im Log als Ausreisser. Wer Leistung misst, muss wissen, welche Ausreisser er selbst erzeugt hat.
Der Autopilot
- Das Fahrzeug fuhr durch die Ballspieler, weil der Fussgängerwächter nur in der Strandphase aktiv war. Jetzt gilt er in jeder Phase, mit engerer Schleife, und nach 0.8 Sekunden Stillstand setzt das Fahrzeug drei Sekunden zurück.
- Es fuhr dreimal in denselben Küstenfelsen. Die Felsen im Wasser waren nicht in der Hindernisliste. Jetzt sind sie es, mit derselben Ausweichlogik wie die Dünenfelsen, 3.4 Meter neben dem Stein auf der freien Seite. Der eine Fels steht ausserdem fünf Meter weiter draussen.
- Es blieb auf der Düne stecken, als kleine Steine und die Felsbrocken dazukamen: die Traktionskontrolle nahm ab sechs Grad Steigung auf 70 Prozent zurück, mit dem Slalom um die Brocken fehlte der Schwung. Jetzt 85 Prozent, und voll, wenn es unter 15 km/h fällt.
- Es verfehlte den Steg. Ein Zielpunkt reicht nicht, um gerade auf eine 1.5 Meter breite Planke zu kommen: jetzt ein Wendepunkt, dann ein Spurfolger auf der Stegachse, Vollgas erst, wenn der Querfehler unter einem Meter und der Kurswinkel unter sechs Grad liegt.
- Mit 55 km/h durch mehr als fünf Zentimeter Wasser auf gesättigtem Sand kam es nicht weit. Das ist kein Fehler, das ist das Modell. Die Choreografie fährt jetzt auf festem Sand und nimmt den Spülsaum nur für die Bremszone.
- Die Drohne flog durch den Berg. Der Rückflug folgte dem Gelände unter ihr, nicht dem Gelände vor ihr. Jetzt schaut sie zwölf Meter voraus, steigt, wo es ansteigt, und wird dort langsamer.

Die Kamera
Die Verfolgerkamera stotterte über der Buckelpiste. Der erste Verdacht war die Bildrate, das Log sagte 17 ms Mittel, 20 ms Maximum, keine Ausreisser. Die Ursache war der Bodenabstand der Kamera: ein harter Mindestabstand, und jede Bodenwelle, die unter der Kamera durchlief, hob sie sofort an und liess sie wenige Bilder später wieder fallen. Jetzt ist der Anhebewert geglättet, schnell hinauf, langsam hinunter. Eine frühere Version derselben Kamera addierte den Bodenabstand jedes Bild erneut auf, bis sie im Orbit stand. Zwei Fehler, ein Mechanismus.

Die Drohnenkamera zeigte immer wieder einen Ring im Bild. Vermutung des Kunden: der gefälschte Gimbal, und die Drohne hat den eigenen Rotor im Bild. Genau so war es. Der Rumpf neigt sich unter der fest auf dem Rumpf montierten Kamera, und bei Nick oder Roll schwenkte die halbdurchsichtige Rotorscheibe von oben ins Bild. Zwei Korrekturen: die Drohne filmt sich nicht mehr selbst, ihr eigener Actor ist für ihre Kamera ausgeblendet. Und der Gimbal hat jetzt Anschläge wie ein echter: er gleicht die Rumpfneigung nur bis 20 Grad Nick und 15 Grad Roll aus, darüber kippt das Bild mit dem Rumpf mit. Beim Backflip dreht sich das Bild einmal über Kopf, und das ist richtig so.
Der Steg
Auf dem Steg ruckelte es, nicht nach dem Eintauchen, sondern während der ganzen Auffahrt. Die Logzeile zeigte die Physik bei 15 ms pro Bild statt 8, die Grafikkarte unbeteiligt. Der Steg bestand aus 38 Planken, jede ein eigener starrer Körper mit Kollisionsbox, und die Reifen waren Dreiecksnetze mit einigen tausend Dreiecken. Vier Netze gegen 38 Boxen, jeden Schritt.
Der Vorschlag des Kunden war der richtige: etwas Unsichtbares drunter. Die Planken sind jetzt nur noch Bild. Gefahren wird auf einem einzigen unsichtbaren, festen Körper mit 18 langen Boxen entlang des Profils, 4.5 Meter, wo der Steg fast gerade ist, 1.5 Meter in der gebogenen Kante. Und die Reifen sind Zylinder statt Netze. Das Bodenmodell rechnet seine Strahlen gegen beides gleich, Einsinken und Traktion im Sand sind unverändert, die Physik liegt jetzt bei 7 ms. Sichtbare Geometrie und Kollisionsgeometrie sind zwei verschiedene Dinge, und die zweite darf so grob sein, wie es die Fahrt erlaubt.
Die Engine
TArray::Add(Array[i])ist in Unreal® 5.8 ein Assert, weil das Element beim Wachsen des Arrays verschoben wird. Zwei Abstürze beim Start, während der Autor zusah. Erst kopieren, dann anhängen.- Ein selbst erzeugtes
ChSystemhat in Chrono 10 keine Schwerkraft. Der Standardwert ist null. Das Fahrzeug schwebt, und es sieht aus wie ein Fehler in der Koordinatenwandlung. - Chrono und Unreal streiten sich um Makros wie
check,PIundTEXT. Ein einziger Header, der die Makros wegsichert und Chrono einbindet, alles andere bindet nie direkt ein. - Vektorparameter eines Materials sind dreidimensional. Wer sie mit zweidimensionalen Texturkoordinaten verrechnet, bekommt kein Ergebnis, sondern das graue Standardmaterial und eine Zeile im Log. Vorher maskieren.
- Ein Rendertarget, das ein Material in 400 einzelnen Zeichenaufrufen mit je eigener Materialinstanz sampelt, kostet 20 ms auf dem Spielthread. Die Wasserphasen fielen auf 24 Bilder pro Sekunde, während die Grafikkarte bei 12 ms lag. Jetzt bekommen nur die grossen Tropfen die Linse, höchstens hundert, die kleinen eine gebündelte Textur.
- Die Python API der Engine erwartet Rotationen als Roll, Pitch, Yaw, anders als der C++ Typ. Die Sonne stand unter dem Horizont, die Szene war schwarz.
- Ein Level ohne Shader Model 6 und ohne den erweiterten Leuchtdichtebereich rendert ebenfalls schwarz. Zwei Ursachen für dasselbe Bild.
- Das Spiel aus Git Bash starten funktioniert nicht, die Shell wandelt den Kartenpfad in einen Windows® Pfad um. PowerShell®.
- Ein weicher dunkler Fleck auf dem Dach war die Distanzfeld Himmelsabschattung, kein Loch im Mesh.
- Das Commandlet für die Materialien hängt nach dem letzten Logeintrag an der Live Coding Verbindung und muss beendet werden.
- Die Windows Game Bar aus einem Skript heraus ordnet den Hotkey dem sendenden Prozess zu und nahm dreimal das falsche Fenster auf. OBS Studio® funktioniert, sobald das Audiogerät mit seiner Endpunkt ID in der Szene steht.
- Patch Skripte, die Quelltext per Textersetzung ändern, müssen idempotent sein. Eines hat den Tick einmal verdoppelt.
Das Finale
Die zweite Hälfte der Demo, Nacht, Parasail und Drohne als Körper, hatte ihre eigenen Fallen. Die Modelle stehen in Teil 6, Teil 7 und Teil 8, hier nur, was schiefging.
- Aus Nacht wurde Tag. Der Mond als zweites Atmosphärenlicht ohne Vorrang rechnete Wasser, Transparenz und Nebel mit und stand als sonnengrosse Scheibe am Himmel. Fix: Priorität für die Sonne, kleine Quellgrösse, Belichtung auf den Tageswert festgesetzt, die Nacht macht das Licht. Mondkugel in 40 km und Sterne in 8 km wanderten gegen den Himmel und standen vor den Wolken; beides auf 200 km hinausgesetzt.
- Der Schirm hing kopfüber und flog rückwärts. Der Auftrieb entlang der Hochachse hat kein rückstellendes Moment; das Wetterfahnenmoment hatte zusätzlich das falsche Vorzeichen. Zwei kleine Änderungen an Angriffspunkt und Vorzeichen.
- Das Boot wurde nie festgefahren, weil die Dünung es weiter hob und senkte. Die Bedingung braucht die Geschwindigkeit über Grund, nicht die Position.
- Die Drohne flog durch Steg und Hügel, weil sie ihre Höhe nach dem Boden unter sich bestimmte statt nach dem Boden voraus. Danach pendelte sie, weil jeder Sprung im Sollpunkt zu einem Sprung in den Rotorkräften wurde; ein Folger mit Bremsprofil und Beschleunigungsgrenze glättet das. Und sie plante ihren Abflug vom gesunkenen Fahrzeug zwei Meter unter Wasser, bis die Mindesthöhe gegen das Maximum aus Gelände und Meeresspiegel rechnete. Nach dem Abschalten der Motoren produzierte der Regler ohne Schub eine ungültige Lage; ohne Schub kein Regler, ein Wächter stellt die letzte gültige Pose wieder her.
- Die Seitenlichter leuchteten 10 Grad zu weit nach achtern, weil ein nach unten geneigter Kreiskegel in der Horizontalen breiter ist als sein Halbwinkel. Mittschiffs, horizontal, harter Rand.
- Die Aufnahme war dreimal stumm. Das Audiogerät in der OBS® Szene war nach einem Headset-Wechsel als Endpunkt-ID ungültig, das Spiel ohne Fokus schaltet den Ton ab, und der Softwareencoder ruckelte. Standardgerät in der Szene, Fokus geregelt zurückgegeben, Lautstärke ohne Fokus in der Engine auf eins, Hardwareencoder.
- Drei Compiler-Baufehler der Engine waren keine Fehler im Modell: eine lokale Variable, die eine äussere verdeckt, eine Konsolenvariable vor ihrer Deklaration benutzt, eine private Eigenschaft mit Blueprint-Zugriff.
Was daraus folgt
Drei Regeln, die den Tag überlebt haben:
- Jede eigene Kraft wird in Chrono Koordinaten formuliert, ist klein, hat eine Obergrenze und wird zuerst im Debugstart geprüft, der sie am härtesten trifft.
- Jeder Trigger hat eine Logzeile mit Start und Ende, und er gilt erst als funktionierend, wenn ein Screenshot zu seiner Zeit das zeigt, was gemeint war.
- Bei Ruckeln zuerst das Log lesen. Die Bildzeit sagt, ob es die Engine ist, und wenn sie es nicht ist, ist es fast immer eine Kamera oder eine Zeitbasis.
Der Klang, der nach anderen Regeln entsteht, ist Teil 4, die Modelle des Finales sind Teil 6 und Teil 7.
Schlüsselwörter: Project Chrono Unreal Fallen, Lenkanschlag Mehrkörpersimulation, Automatikgetriebe Simulation Sand, Wählhebel 2, Trigger Zeitbasis Simulation, Autopilot Fussgängererkennung, Kamera Stottern Bodenabstand, Unreal Engine 5.8 Plugin, Serious Gaming, Simulation Beratung Schweiz, swissEmbedded
Steckt eure Simulation in einer dieser Fallen? Wir finden die Ursache im Modell, nicht im Zufall, und bauen die Regeln ein, die sie beim nächsten Mal verhindern.
Unreal® und Unreal Engine® sind Marken oder eingetragene Marken von Epic Games, Inc. in den USA und anderswo; diese Artikel sind von Epic Games weder unterstützt noch geprüft. Project Chrono ist ein Open-Source-Projekt der University of Wisconsin-Madison und des Politecnico di Milano unter BSD-3-Lizenz.