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

240 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <out.json>`, `--trace-mirror <out.txt>`; Vergleich mit `python3 tools/port/trace_diff.py <a> <b>`.