- Projekt mit Unity 6.3 LTS (6000.3.20f1, Personal) geöffnet: ProjectSettings, .meta-Dateien, URP-Global-Settings, packages-lock.json. - Manifest auf die benötigten Pakete zurückgeführt; Unity hatte beim ersten Öffnen Vorlagenpakete ergänzt (Purchasing, Analytics, Collab, XR, 2D, Navigation). - Test Runner: Die U1-Suiten laufen unverändert als EditMode-Tests; mit LUNE_TEST_REPORT schreiben .NET und Unity dasselbe JSON-Protokoll (SuiteReport). - Golden-Vergleich zusätzlich je Fachbereich (Gold, HP, Positionen, Türme, Ancients, Wellen, Helden, Boss, Gewinner, ...). Nur Bericht; Testwerte und Prüfungsanzahl unverändert. - Diagnose: FloatProbe (Kern-Arithmetik gegen Godot-Bits und Auswertungsart der Laufzeit), DivergenceTrace und tools/port/trace_diff.py (erster abweichender Schritt), CoreBenchmark für .NET und Editor, CoreBatch als -executeMethod-Einstieg. - .NET-Harness baut nach tools/out/, weil Unity Temp/ beim Beenden löscht. - .gitignore ignoriert nur noch die csproj/sln im Projektwurzelverzeichnis; die Projektdateien des .NET-Harness fehlten deshalb in U1 und kommen jetzt dazu. Stand dieses Commits im Unity-Editor (Mono): 4 von 5 Suiten grün; golden 152/157, weil Mono float32-Zwischenwerte doppelt genau rechnet (Teil 2/2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.1 KiB
Python
77 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Compares two step traces of DivergenceTrace (docs/port/U1.5-UNITY.md).
|
|
|
|
Reports the first step where raw bits differ (positions, hit points) and the first step
|
|
where a decision differs (alive, target, hits, zone, combat end), with the units involved.
|
|
Usage: python3 tools/port/trace_diff.py <reference trace> <other trace>
|
|
"""
|
|
import struct
|
|
import sys
|
|
|
|
|
|
def load(path):
|
|
steps = []
|
|
current = None
|
|
for line in open(path, encoding="utf-8"):
|
|
line = line.rstrip("\n")
|
|
if line.startswith("step "):
|
|
parts = line.split()
|
|
current = {"step": int(parts[1]), "t": float(parts[3]), "finished": parts[5], "units": {}}
|
|
steps.append(current)
|
|
elif line.startswith(" u ") and current is not None:
|
|
discrete, bits = line[4:].split(" | ")
|
|
fields = discrete.split()
|
|
b = bits.split()
|
|
current["units"][int(fields[0])] = {
|
|
"discrete": " ".join(fields[1:]),
|
|
"hp": int(b[1], 16),
|
|
"x": int(b[3], 16),
|
|
"y": int(b[4], 16),
|
|
}
|
|
return steps
|
|
|
|
|
|
def f32(bits):
|
|
return struct.unpack("<f", struct.pack("<i", bits if bits < 2**31 else bits - 2**32))[0]
|
|
|
|
|
|
def f64(bits):
|
|
return struct.unpack("<d", struct.pack("<Q", bits))[0]
|
|
|
|
|
|
def main():
|
|
ref, other = load(sys.argv[1]), load(sys.argv[2])
|
|
print(f"steps: reference {len(ref) - 1}, other {len(other) - 1}")
|
|
first_bits = first_decision = None
|
|
worst_pos = 0.0
|
|
worst_hp = 0.0
|
|
for a, b in zip(ref, other):
|
|
for uid in sorted(set(a["units"]) | set(b["units"])):
|
|
ua, ub = a["units"].get(uid), b["units"].get(uid)
|
|
if ua is None or ub is None:
|
|
if first_decision is None:
|
|
first_decision = (a["step"], uid, ua and ua["discrete"], ub and ub["discrete"])
|
|
continue
|
|
if (ua["x"], ua["y"], ua["hp"]) != (ub["x"], ub["y"], ub["hp"]):
|
|
if first_bits is None:
|
|
first_bits = (a["step"], uid)
|
|
if first_decision is None:
|
|
worst_pos = max(worst_pos, abs(f32(ua["x"]) - f32(ub["x"])), abs(f32(ua["y"]) - f32(ub["y"])))
|
|
worst_hp = max(worst_hp, abs(f64(ua["hp"]) - f64(ub["hp"])))
|
|
if ua["discrete"] != ub["discrete"] and first_decision is None:
|
|
first_decision = (a["step"], uid, ua["discrete"], ub["discrete"])
|
|
if a["finished"] != b["finished"] and first_decision is None:
|
|
first_decision = (a["step"], "combat end", a["finished"], b["finished"])
|
|
print("first bit difference: step %s unit %s" % first_bits if first_bits else "no bit difference")
|
|
if first_decision:
|
|
step, uid, da, db = first_decision
|
|
print(f"first decision difference: step {step} (t = {step * 0.05:.2f} s) unit {uid}")
|
|
print(f" reference: {da}")
|
|
print(f" other: {db}")
|
|
print(f"largest deviation before it: position {worst_pos:.3e} m, hp {worst_hp:.3e}")
|
|
else:
|
|
print("no decision difference")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|