champions-of-luneraria/docs/port/U1.5-UNITY.md
Martin Stickelbroeck 9bf397e765 U1.5 Unity Core Integration (2/2): explizite float32-Rundung, Nachweise
Befund: Unitys Mono 6.13 rechnet float32-Zwischenwerte doppelt genau
(ECMA-335 I.12.1.3). Der erste Editor-Lauf fand 5 von 157 Golden-
Abweichungen: Blickrichtung bis 1,6e-3 rad; in mirror_match Runde 7 verliert
eine Einheit in Schritt 599 ihr Ziel, und der Kampf dauert 4,8 s länger.
Eine Toleranz ist dafür nicht begründbar.

Maßnahme: Vec2 rundet jeden Zwischenwert explizit auf float32 (conv.r4).
Keine Regeländerung, R1 unverändert. .NET 8: 402/402, Golden 0, Trace
byteidentisch zu vorher. Unity-Editor (Release und Debug): 402/402, Golden
bitgleich in allen Bereichen, Float-Probe 4000/4000.

- CoreIntegrationTests: keine Engine-Referenzen im Kern, Float-Arithmetik
  wie Godot.
- docs/port/U1.5-UNITY.md: Versionen, Lizenz, Pakete, Tests, Golden je
  Bereich, Float-Befund, Editor-Benchmark, fehlende Build-Module.
- docs/port/KNOWN-BEHAVIOR-DIFFERENCES.md mit "Legacy defects retained for
  parity" (R1) und F1.
- Protokolle unter docs/port/logs/u1_5/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:22:36 +02:00

13 KiB
Raw Blame History

U1.5 · Unity-Integrationsprüfung

Stand: 30. September 2026. Ergebnis: grün. Der Regelkern aus U1 läuft im Unity-Editor mit allen 402 Prüfungen. Die Golden-Szenarien sind bitgleich zur Godot-Referenz. Dafür war eine rein numerische Korrektur in Vec2 nötig; Regeln und R1 sind unverändert (Abschnitt „Float-Befund“).

Umgebung

Punkt Wert
Unity 6000.3.20f1 (Revision c9ba695d4f07) = Unity 6.3 LTS, Apple-Silicon-Editor
Lizenz Unity Personal (Typ „Assigned“, unbefristet; laut Editor-Log). HasPro = false
URP 17.3.0; Core RP 17.3.0, Shader Graph 17.3.0
Editor-Laufzeit Mono 6.13.0 (explicit/dc7ab1aa)
Scripting Backend (Standalone) Mono (Mono2x); IL2CPP ist nicht eingestellt
API Compatibility Level .NET Standard 2.1. Unity-Enum-Wert 6; ToString() zeigt den Altnamen NET_Standard_2_0, der kompilierte Kern referenziert netstandard 2.1.0.0
Sprachstand C# 9 (Unity-Standard; der .NET-Harness baut mit derselben Stufe)
Code Optimization Batchmodus: Release. Interaktiver Editor: Debug. Beide Modi sind geprüft (-debugCodeOptimization)
Test Framework com.unity.test-framework 1.6.0 (NUnit-Engine 3.5)
Farbraum, Render-Pipeline-Asset Gamma, keines zugewiesen. Beides ist Teil von U2B (Linear, URP-Asset)
Build-Module nur macOS. Windows und Linux fehlen (Abschnitt „Fehlende Build-Module“)
Rechner Mac mit Apple M5, macOS 27.0

Rohdaten: logs/u1_5/unity-environment.json.

Projektstruktur

Pfad Inhalt
Assets/Luneraria/Core/ Regelkern, Assembly Luneraria.Core mit noEngineReferences
Assets/Luneraria/Tests/Core/ Suiten, Golden-Dateien, Diagnostics/ (Float-Probe, Schritt-Trace, Benchmark), Unity/ (Test-Runner-Adapter, Batch-Einstiege). Assembly Luneraria.Core.Tests, nur Editor
Assets/UniversalRenderPipelineGlobalSettings.asset, Assets/DefaultVolumeProfile.asset von URP beim ersten Öffnen angelegt; U2B räumt sie mit dem URP-Asset nach Assets/Luneraria/Settings/
ProjectSettings/, Packages/packages-lock.json von Unity 6.3 erzeugt und eingecheckt
tools/dotnet/ .NET-Harness. Baut nach tools/out/, weil Unity Temp/ beim Beenden löscht

Alle Dateien unter Assets/ haben .meta-Dateien; die GUIDs sind eingecheckt.

Pakete

Unity hat beim ersten Öffnen (ohne ProjectSettings/) die Vorlagenpakete eines neuen Projekts in das Manifest geschrieben. Das Manifest ist wieder auf den geplanten Umfang zurückgeführt.

Behalten sind:

  • URP und Shader Graph.
  • Input System, uGUI, Recorder (zieht Timeline nach) und Newtonsoft JSON.
  • Test Framework.
  • Die IDE-Integrationen für Rider und Visual Studio/VS Code (nur Editor).
  • Die Engine-Module aus U1.

Entfernt sind:

  • Purchasing und Analytics (samt Unity Services).
  • Collab-Proxy (Unity Version Control), Multiplayer Center.
  • XR Legacy Input Helpers, 2D Sprite/Tilemap, AI Navigation.
  • 25 zusätzliche Moduleinträge.

Gründe:

  • Keine Telemetrie und keine In-App-Käufe.
  • Versionsverwaltung ist Git.
  • Die Bewegung gehört dem Kern: Ein NavMesh in der Szene würde Regeln in die Darstellung ziehen.

Beim Entfernen wurde nichts heruntergeladen. Beim ersten Öffnen hatte Unity die Vorlagenpakete aus der Registry geholt (upm.log).

Kern ohne Engine-Abhängigkeit

  • Luneraria.Core.asmdef setzt noEngineReferences: true: Jede Verwendung von UnityEngine wäre ein Kompilierfehler.
  • Die in Unity kompilierte Assembly referenziert nur netstandard 2.1.0.0. Der Test CoreIntegrationTests.CoreHasNoEngineReferences prüft das bei jedem Lauf.
  • Der Kern kompiliert in Unity ohne Warnung.

Tests im Unity Test Runner

Die U1-Suiten laufen unverändert als EditMode-Tests (CoreSuiteTests), mit denselben Prüfungen und Werten. Die Golden-Referenzen wurden nicht neu erzeugt.

Suite Prüfungen Editor, Release Editor, Debug .NET 8
content 75 grün, 0,01 s grün, 0,01 s grün
core 91 grün, 5,3 s grün, 11,9 s grün
m2 73 grün, 3,4 s grün, 6,2 s grün
golden 157 grün, 13,4 s grün, 24,5 s grün
determinism 6 grün, 13,4 s grün, 24,8 s grün
gesamt 402 402/402 402/402 402/402

Dazu kommen zwei Unity-spezifische Integrationstests (CoreIntegrationTests), beide grün:

  • Keine Engine-Referenzen: Der Kern referenziert keine Unity-Assembly.
  • Float-Arithmetik wie Godot: Alle Vec2-Operationen stimmen in 4.000 von 4.000 Stichproben mit Godot überein.

Protokolle:

  • logs/u1_5/editmode-results-{release,debug}.xml (NUnit).
  • logs/u1_5/core-tests-unity-editor-{release,debug}.json und core-tests-dotnet8.json: gleiches Format, mit den Golden-Notizen.

Golden-Vergleich in Unity

Verglichen werden 19 Szenarien, 120 Snapshots und 51.221 Werte (Umfang siehe U1-RULES.md). Neu ist die Auswertung je Fachbereich; sie ist reiner Bericht, Prüfungen und Toleranzen sind unverändert.

Bereich Werte Abweichungen im Editor größte Zahlenabweichung
Gold (Konten, Entwurf, Einnahmen, Ausgaben, Gewinne, Einkommen) 3.584 0 0
HP (Einheiten, Schilde) 3.420 0 0
Positionen und Lanefortschritt 3.420 0 0
Türme (Gebäude-HP, verlorene Gebäude) 1.994 0 0
Ancients (HP, zerstörte Kerne) 246 0 0
Wellen (Zusammensetzung, Werte, Kills je Lane, Truppenzahl) 731 0 0
Helden (Stufe, Erfahrung, Talente, Items) 1.512 0 0
Boss (Zustand, Anspruch, Log, Grotte, Boss-HP) 1.559 0 0
Gewinner, Phase, Endgrund 695 0 0
Ziele 1.140 0 0
Blickrichtung 1.140 0 0
Käufe, Armeen, Buchungen 9.311 0 0
Ereignisse 6.937 0 0
übrige Einheitenfelder, Kampf, Befehle, Runden, Ankündigungen 15.532 0 0

Das gilt für Release und Debug. Die Summe der Tabelle ist 51.221.

Float-Befund

Erster Lauf: nicht bitgleich

Der erste Lauf im Editor fand in 5 von 157 Golden-Prüfungen Unterschiede (logs/u1_5/editmode-results-before-fix.xml):

Snapshot Unterschied
start_three_lanes/t10, lane_both_lost/t35, base_tower_attack/t60, high_wave_21/t20 Blickrichtung bis 1,6·10⁻³ rad (Toleranz 10⁻⁴)
mirror_match/round-7 Kampfdauer 46,10 s statt 41,30 s (922 statt 826 Schritte)

Weitere Messwerte:

  • Positionen wichen bis 0,22 mm ab (Toleranz 1 mm).
  • Die berechnete Lanegeometrie wich um 2,4·10⁻⁷ m ab.
  • Gold, HP, Türme, Ancients, Helden, Boss und Gewinner waren in allen Snapshots gleich.

Ursache

Unitys Mono 6.13 wertet float32-Ausdrücke mit Zwischenwerten doppelter Genauigkeit aus und rundet erst beim Speichern. ECMA-335 (Partition I, 12.1.3) erlaubt das. .NET 8 und Godot runden nach jeder Operation.

Nachweis mit FloatProbe: Er rechnet die 4.000 Eingaben von tools/godot-golden/float_probe.gd mit den Kernfunktionen und vergleicht die Bits mit Godot.

Laufzeit x*x + y*y wie … a + (b − a)·w wie …
.NET 8 float32 je Operation: 4.000/4.000 float32 je Operation: 4.000/4.000
Unity-Mono doppelt genauer Zwischenwert: 4.000/4.000 (float32 nur 3.049, genau dort, wo beide Varianten zusammenfallen) doppelt genauer Zwischenwert: 4.000/4.000 (float32 nur 2.620)

Kernoperationen vor der Korrektur, identisch mit Godot:

Operation Treffer
Length 3.520/4.000
LengthSquared 3.049
DistanceTo 3.260
DistanceSquaredTo 2.578
Dot 2.560
Normalized 3.524
Lerp 1.786
LimitLength 3.530
Einzeloperationen (*, /, atan2) 4.000

Die Abweichung liegt bei 1–2 ulp. Bei Auslöschung nahe null sind es bis 1.709 ulp, absolut aber winzig.

Rohdaten: logs/u1_5/float-probe-*.json.

Wirkung auf Entscheidungen

Ja, die Unterschiede ändern Entscheidungen. DivergenceTrace protokolliert mirror_match Runde 7 in beiden Laufzeiten Schritt für Schritt; tools/port/trace_diff.py vergleicht:

  • Ab Schritt 0: Die Positionsbits unterscheiden sich um höchstens 1,9 µm, bis Schritt 450 um höchstens 8 µm.
  • Ab etwa Schritt 470: Die gegenseitige Verdrängung der Einheiten auf der oberen Lane verstärkt den Unterschied auf 3 mm (Schritt 475), 19 mm (500) und 17 cm (600).
  • Schritt 599 (t = 29,95 s): Einheit 54 (Truppe Blau, obere Lane) behält in der Referenz Ziel 37 und hat in Unity keines. Der Kampf endet 4,8 s später. Beute, Gebäude, Erfahrung und Sieger dieser Runde blieben zufällig gleich.

Die Blickrichtung wird vom Kern nur geschrieben, nie gelesen; sie ist ein reiner Darstellungswert. Ihre Abweichung folgt aus den Sub-Millimeter-Positionen, weil die Richtung schon ab 1 mm Bewegung neu gesetzt wird.

Bewertung und Maßnahme

Eine Toleranz ist hier fachlich nicht begründbar. Die Kampfdynamik verstärkt Unterschiede im letzten Bit bis zu anderen Zielentscheidungen. Eine größere Toleranz würde das nur verdecken. Für die Blickrichtung allein wäre eine weitere Toleranz vertretbar; sie ist aber nicht nötig.

Maßnahme: Vec2 rundet jeden Zwischenwert explizit auf float32 ((float) → conv.r4); ECMA-335 garantiert diese Rundung.

  • Die Änderung betrifft nur Assets/Luneraria/Core/Model/Vec2.cs. Alle anderen Regelgrößen sind double, und float-Ergebnisse werden dort exakt erweitert.
  • Keine Regel ändert sich, R1 bleibt.
  • Das ist keine Anpassung an .NET: Maßstab ist Godots Vector2 mit float32-Arithmetik. Die Casts erzwingen genau diese Semantik auf jeder ECMA-Laufzeit: Mono im Editor und im Player, IL2CPP, .NET.
  • Doppelte Rundung ist dabei unschädlich. Bei +, −, ×, ÷ und √ hat double (53 Bit) mindestens 2·24 + 2 Bit, also ist jede über double gerundete Operation exakt das float32-Ergebnis (Figueroa, 1995).

Nachweis:

Prüfung Ergebnis
.NET 8 vorher/nachher 402/402 und Golden-Abweichung 0 unverändert; Trace mirror_match Runde 7 byteidentisch (SHA-256 gleich)
Unity-Editor nachher, Release und Debug 402/402, Golden-Abweichung 0 in allen Kategorien und Bereichen; Lanegeometrie 0 m
Float-Probe nachher alle Kernoperationen 4.000/4.000 in .NET, Editor-Release und Editor-Debug
Trace nachher Unity = .NET: 826 Schritte, kein abweichendes Bit
Absicherung CoreIntegrationTests.CoreFloatArithmeticMatchesGodot im Test Runner

Die Laufzeit selbst rechnet weiter doppelt genau; die Probe zeigt das in evaluation_matches. Der Kern ist davon jetzt unabhängig.

Offen: IL2CPP sowie Player unter Windows 11 und Fedora 44 sind nicht gemessen, weil die Build-Module fehlen. Erwartet wird Gleichheit, weil die Rundung jetzt explizit ist. Der Nachweis steht aus.

Leistung

Das ist ein Editor-Benchmark, kein Player-Wert: Mono-JIT im Editor, gemessen im Batchmodus. Fixture wie in U1: Runde 13, sechs Armeen am Versorgungslimit, 120 bewegliche Einheiten; dazu die Bot-Partien der Seeds 1–3 (32 Runden). Alle Läufe fanden nacheinander auf demselben Mac statt.

Laufzeit Simulationsschritt (Median 5 × 200) Bot-Partien Seeds 1–3
Godot 4.7.2, GDScript 1,600 ms 21,0 s
Unity-Editor, Mono, Debug 1,004 ms 11,0 s
Unity-Editor, Mono, Release 0,514–0,518 ms 6,76–6,82 s
.NET 8, Release 0,24–0,26 ms 1,20–1,55 s

Einordnung:

  • Im Editor ist der Kern 3,1-mal so schnell wie Godot und halb so schnell wie .NET 8.
  • Ein Schritt deckt 50 ms Spielzeit ab. 0,52 ms sind damit rund 1 % eines Kerns.
  • Der größere Abstand bei den Bot-Partien kommt aus der Bot-Planung mit vielen Zuweisungen (Mono-GC).
  • Player-Werte (Mono/IL2CPP) folgen, sobald die Build-Module installiert sind.

Rohdaten: logs/u1_5/sim-bench-*.json.

Fehlende Build-Module

Im Editor 6000.3.20f1 ist nur MacStandaloneSupport installiert. Für die Zielplattformen fehlen die folgenden Unity-Hub-Module. Sie werden erst nach Freigabe installiert:

Modul (Unity Hub) Hub-ID wofür
Windows Build Support (Mono) windows-mono Windows-11-Player (x64) vom Mac aus
Linux Build Support (Mono) linux-mono Fedora-44-Player (x86_64) vom Mac aus
Linux Build Support (IL2CPP) linux-il2cpp optional: IL2CPP-Player für Linux; braucht zusätzlich die Linux-Toolchain aus dem Package Manager (Download)
Linux Dedicated Server Build Support linux-server optional, erst für U4: dedizierter Host ohne Grafik

Windows-IL2CPP lässt sich nur auf einem Windows-Rechner bauen, nicht vom Mac aus.

Hinweis zu den Testrechnern: Die vorhandenen Parallels-VMs sind ARM. Windows 11 ARM führt x64-Player per Emulation aus. Fedora ARM kann einen x86_64-Linux-Player nicht direkt starten; dafür braucht es ein x86_64-System.

Befehle

UNITY=/Applications/Unity/Hub/Editor/6000.3.20f1/Unity.app/Contents/MacOS/Unity
LUNE_TEST_REPORT="$PWD/docs/port/logs/u1_5/core-tests-unity-editor-release.json" "$UNITY" -batchmode -projectPath "$PWD" -runTests -testPlatform EditMode -testResults "$PWD/docs/port/logs/u1_5/editmode-results-release.xml" -logFile -
"$UNITY" -batchmode -projectPath "$PWD" -executeMethod Luneraria.Core.Tests.CoreBatch.Benchmark -logFile -

Weitere Einstiege:

  • CoreBatch.FloatProbe, CoreBatch.EnvironmentReport, CoreBatch.TraceMirrorMatch.
  • Mit -debugCodeOptimization läuft der Editor im Debug-Modus.
  • .NET: sh tools/run_core_tests.sh, --float-probe <out.json>, --trace-mirror <out.txt>; Vergleich mit python3 tools/port/trace_diff.py <a> <b>.