scopedvr-map-editor/tests/test_editor_svmap.gd

167 lines
7.4 KiB
GDScript
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.

extends SceneTree
## Editor-Tests (headless): Karte t_5badf371 — svmap laden/speichern.
## Ausführen: godot --headless --path . --script res://tests/test_editor_svmap.gd
## Exit 0 = alles grün. Läuft OHNE UI (nur SvmapIO/SvmapDocument).
var failures := 0
var checks := 0
func _init() -> void:
# user:// persistiert über Godot-Läufe — vor UND nach dem Test aufräumen
# (Lektion aus t_74976e9f: Stale-Reste maskierten sonst Verifikationen).
_cleanup()
_run_tests()
_cleanup()
if failures == 0:
print("ALL OK: %d checks" % checks)
quit(0)
else:
print("FAILED: %d/%d checks" % [failures, checks])
quit(1)
func _cleanup() -> void:
for path in ["user://test_roundtrip.svmap", "user://test_roundtrip2.svmap"]:
if FileAccess.file_exists(path):
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
func _check(cond: bool, label: String) -> void:
checks += 1
if cond:
print(" ok: %s" % label)
else:
failures += 1
print(" FAIL: %s" % label)
func _errors_contain(errors: Array, needle: String) -> bool:
for e in errors:
if str(e).contains(needle):
return true
return false
func _run_tests() -> void:
print("== Neue Map: default-Dokument ==")
var doc := SvmapDocument.create_new("Testmap", 1024.0, "abend")
_check(int(doc["format_version"]) == 1, "format_version 1")
_check(str(doc["info"]["name"]) == "Testmap", "Name übernommen")
_check(str(doc["weather"]) == "abend", "Wetter übernommen")
_check((doc["objects"] as Array).is_empty(), "leere Objektliste")
_check((doc["spawn_zones"]["0"]["points"] as Array).size() == 4, "Team 0: 4 Default-Spawns")
_check((doc["spawn_zones"]["1"]["points"] as Array).size() == 4, "Team 1: 4 Default-Spawns")
var terrain: Dictionary = doc["terrain"]
_check(int(terrain["resolution"]) == 129, "Default-Auflösung 129 (Editor-MVP-Perf-Limit, Karte t_9aba04f1)")
var raw := Marshalls.base64_to_raw(str(terrain["heightmap_r16_b64"]))
_check(raw.size() == 129 * 129 * 2, "Höhenfeld-Länge = resolution² × 2")
var flat := true
for i in raw.size():
if raw[i] != 0:
flat = false
break
_check(flat, "Default-Höhenfeld flach (alles 0)")
print("== Speichern (Validierung vor dem Schreiben) ==")
var save1 := SvmapIO.save_to_file("user://test_roundtrip.svmap", doc)
_check(save1["ok"], "save_to_file ok: %s" % str(save1["errors"]))
print("== Roundtrip: speichern → laden → speichern → byte-identisch ==")
var load1 := SvmapIO.load_from_file("user://test_roundtrip.svmap")
_check(load1["ok"], "load_from_file ok: %s" % str(load1["errors"]))
var doc2: Dictionary = load1["data"]
_check(str(doc2["info"]["name"]) == "Testmap", "Name im Roundtrip erhalten")
_check(str(doc2["weather"]) == "abend", "Wetter im Roundtrip erhalten")
_check((doc2["spawn_zones"]["1"]["points"] as Array).size() == 4, "Spawns im Roundtrip erhalten")
var save2 := SvmapIO.save_to_file("user://test_roundtrip2.svmap", doc2)
_check(save2["ok"], "Re-Save ok")
var text1 := FileAccess.get_file_as_string("user://test_roundtrip.svmap")
var text2 := FileAccess.get_file_as_string("user://test_roundtrip2.svmap")
_check(text1 == text2, "Dateien byte-identisch (Prüfsumme stabil über save/load/save)")
print("== Server-Kompatibilität: Editor-Prüfsumme == Loader-Kanonisierung ==")
# Der Loader im Match-Server nutzt exakt: kanonisieren → stringify(„ ") →
# sha256_text. Der Editor muss denselben Hash erzeugen (geprüft an der
# geladenen Map, die ein checksum-Feld trägt):
var doc_signed: Dictionary = _deep_copy(doc2)
SvmapIO.sign_map(doc_signed)
var payload: Dictionary = SvmapIO._canonicalize(doc_signed)
payload.erase("checksum")
var expected: String = JSON.stringify(payload, " ").sha256_text()
_check(str(doc_signed["checksum"]["value"]) == expected, "sign_map-Hash = Kanonisierungs-Hash")
print("== Negative Fälle (Ablehnung mit verständlicher Meldung) ==")
var res: Dictionary
res = SvmapIO.parse_and_validate("{{{definitiv kaputt")
_check(not res["ok"] and _errors_contain(res["errors"], "V2"), "kaputtes JSON → V2")
res = SvmapIO.parse_and_validate('{"format_version": 2}')
_check(not res["ok"] and _errors_contain(res["errors"], "V3"), "falsche Version → V3")
var bad: Dictionary = _deep_copy(doc2)
bad["weather"] = "sturm"
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V12"), "unbekanntes Wetter → V12")
bad = _deep_copy(doc2)
(bad["spawn_zones"]["0"]["points"] as Array)[1]["id"] = "A1"
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V11"), "Spawn-ID-Dublette → V11")
bad = _deep_copy(doc2)
(bad["spawn_zones"]["1"]["points"] as Array).resize(3)
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V11"), "zu wenige Spawns → V11")
bad = _deep_copy(doc2)
(bad["objects"] as Array).append({"id": "taube_aus_dem_wald", "pos": [0.0, 1.0, 0.0], "rot_y_deg": 0.0, "scale": 1.0})
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V7"), "unbekannte Objekt-ID → V7")
bad = _deep_copy(doc2)
(bad["objects"] as Array).append({"id": "crate_wood", "pos": [99999.0, 0.0, 0.0], "rot_y_deg": 0.0, "scale": 1.0})
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V9"), "Objekt außerhalb der Grenzen → V9")
bad = _deep_copy(doc2)
bad["terrain"]["resolution"] = 700
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V6"), "Auflösung zu groß → V6")
bad = _deep_copy(doc2)
bad["terrain"]["heightmap_r16_b64"] = "AAAA"
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V5"), "Höhenfeld falsche Länge → V5")
bad = _deep_copy(doc2)
bad["checksum"]["value"] = "0".repeat(64)
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V13"), "manipulierte Prüfsumme → V13")
bad = _deep_copy(doc2)
bad["info"]["author"] = ""
res = SvmapIO.parse_and_validate(JSON.stringify(bad))
_check(not res["ok"] and _errors_contain(res["errors"], "V4"), "leerer Autor → V4")
print("== Poly-Budget (injiziertes Manifest) ==")
var mega_manifest: Dictionary = {"objects": {"mega": {"poly": 2_000_000, "collision": {"shape": "box", "size": [1, 1, 1]}}}}
var budget_doc := SvmapDocument.create_new("Budget", 512.0, "mittag")
(budget_doc["objects"] as Array).append({"id": "mega", "pos": [0.0, 1.0, 0.0], "rot_y_deg": 0.0, "scale": 1.0})
res = SvmapIO.parse_and_validate(JSON.stringify(budget_doc), mega_manifest)
_check(not res["ok"] and _errors_contain(res["errors"], "V8"), "Poly-Budget überschritten → V8")
print("== Poly-Nutzung (Editor-Budgetanzeige) ==")
var usage_doc := SvmapDocument.create_new("Usage", 512.0, "mittag")
(usage_doc["objects"] as Array).append({"id": "crate_wood", "pos": [0.0, 0.6, 0.0], "rot_y_deg": 0.0, "scale": 2.0})
# crate_wood poly=12, scale²=4 → 48
_check(int(SvmapDocument.poly_usage(usage_doc, SvmapIO._manifest_or({}))) == 48, "poly_usage = poly × scale²")
# Sentinel: falls ein Script-Error einen Abschnitt abbricht, fehlt dieser
# Check → kein falsches „ALL OK".
_check(true, "Alle Testabschnitte liefen bis zum Ende durch")
func _deep_copy(value: Variant) -> Variant:
return SvmapIO._canonicalize(value)