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>
240 lines
13 KiB
Markdown
240 lines
13 KiB
Markdown
# 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>`.
|