# 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 ```bash UNITY=/Applications/Unity/Hub/Editor/6000.3.20f1/Unity.app/Contents/MacOS/Unity ``` ```bash 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 - ``` ```bash "$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 `, `--trace-mirror `; Vergleich mit `python3 tools/port/trace_diff.py `.