scopedvr-map-editor/tests/test_skybox.gd
slyphax fcc7f16f1c Skybox-Auswahl im Map-Panel: 13 neue Himmel, Vorschau, Undo/Redo, V17 über Katalog
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 15:30:38 +01:00

213 lines
8.7 KiB
GDScript

extends SceneTree
## Editor-Test (headless): Karte t_f72913d4 — Atmosphäre & Skybox.
## Ausführen: godot --headless --path . --script res://tests/test_skybox.gd
## Exit 0 = alles grün.
##
## T1 svmap-Validierung (V17): weather_sky optional; "sky_panorama"/
## "prozedural" gültig, Quatsch/nicht-String → V17-Fehler; alte Karte
## ohne Feld validiert unverändert.
## T2 Map-Panel „Himmel“: doc-Roundtrip (schreiben = sky_panorama,
## „Standard“ = Feld ENTFERNEN, Auffüllen aus dem doc).
## T3 Editor-Vorschau (map_view_builder): weather_sky=sky_panorama →
## PanoramaSkyMaterial aus dem Client-Nachbar-Klon + ACES-Tonemap +
## Fog am Skybox-Horizont; ohne Feld → ProceduralSky (alte Maps
## unverändert).
var failures := 0
var checks := 0
var main
var map_panel
func _initialize() -> void:
_run_all.call_deferred()
func _check(cond: bool, label: String) -> void:
checks += 1
print("%s: %s" % ["ok " if cond else "FAIL", label])
if not cond:
failures += 1
func _run_all() -> void:
var scene: PackedScene = load("res://scenes/main.tscn")
_check(scene != null, "scenes/main.tscn laedt")
if scene == null:
_finish()
return
main = scene.instantiate()
root.add_child(main)
await process_frame
await process_frame
map_panel = main.get("map_mod")
_check(map_panel != null, "Map-Panel ist in main eingebaut")
if map_panel == null:
_finish()
return
await _t1_validierung()
await _t2_panel()
await _t3_vorschau()
await _t4_auswahl_undo()
_finish()
func _t1_validierung() -> void:
print("== T1: svmap-Validierung weather_sky ==")
# Alte Karte ohne Feld: unverändert gültig.
var alte: Dictionary = main.doc.duplicate(true)
alte.erase("weather_sky")
SvmapIO.sign_map(alte)
var f0: PackedStringArray = SvmapIO.validate_map(alte)
_check(f0.is_empty(), "T1 Karte ohne weather_sky validiert ohne Fehler (%s)" % str(f0))
# Gültige Werte (mit frischer Prüfsumme — Feld fließt in die Payload).
for wert in ["sky_panorama", "prozedural", "sky_03", "sky_115"]:
var d: Dictionary = main.doc.duplicate(true)
d["weather_sky"] = wert
SvmapIO.sign_map(d)
var f: PackedStringArray = SvmapIO.validate_map(d)
_check(f.is_empty(), "T1 weather_sky='%s' validiert (%s)" % [wert, str(f)])
# Ungültige Werte → V17 (_validate_core ohne Prüfsumme, wie test_symmetry_core).
for wert in ["panorama", "sky_999", 42]:
var d2: Dictionary = main.doc.duplicate(true)
d2["weather_sky"] = wert
var f2: PackedStringArray = SvmapIO._validate_core(d2)
var hat_v17 := false
for e in f2:
if str(e).begins_with("V17"):
hat_v17 = true
_check(hat_v17, "T1 weather_sky=%s → V17-Fehler (%s)" % [str(wert), str(f2)])
func _t2_panel() -> void:
print("== T2: Map-Panel Himmel-Option ==")
var prop_sky: OptionButton = map_panel.get("prop_sky")
_check(prop_sky != null, "T2 prop_sky existiert")
if prop_sky == null:
return
_check(prop_sky.item_count == 1 + SkyboxKatalog.EINTRAEGE.size(),
"T2 Himmel-Optionen: Standard + %d Katalog-Einträge (ist %d)" % [SkyboxKatalog.EINTRAEGE.size(), prop_sky.item_count])
_check(prop_sky.item_count == 15, "T2 15 Einträge (Standard, Wüste, 13 neue)")
_check(str(prop_sky.get_item_text(1)).contains("Wüste"),
"T2 Eintrag 2 = Wüste (ist „%s“)" % str(prop_sky.get_item_text(1)))
# doc mit Panorama → UI zeigt Pack-Skybox.
main.doc["weather_sky"] = "sky_panorama"
map_panel.call("refresh_values")
_check(prop_sky.selected == 1, "T2 doc=sky_panorama → UI Pack-Skybox (ist %d)" % prop_sky.selected)
# „Prozedural (Standard)“ → Feld ENTFERNEN (kein "prozedural"-Wert im doc).
map_panel.call("_on_sky_changed", 0)
_check(not main.doc.has("weather_sky"), "T2 Standard entfernt das Feld")
_check(bool(main.get("dirty")), "T2 Änderung markiert dirty")
# „Pack-Skybox“ → Feld = sky_panorama (Client-Vertrag, V17-Wert).
map_panel.call("_on_sky_changed", 1)
_check(str(main.doc.get("weather_sky", "")) == "sky_panorama",
"T2 Pack-Skybox schreibt weather_sky=sky_panorama")
# UI-Zustand nach externer doc-Änderung korrekt auffüllen.
map_panel.call("refresh_values")
_check(prop_sky.selected == 1, "T2 refresh_values liest das Feld (Index 1)")
# Für T3-Baseline wieder auf Standard.
map_panel.call("_on_sky_changed", 0)
_check(not main.doc.has("weather_sky"), "T2 Aufräumen: Feld entfernt")
func _t3_vorschau() -> void:
print("== T3: Editor-Vorschau (map_view_builder) ==")
var vb = main.view_builder
_check(vb != null, "T3 view_builder vorhanden")
if vb == null:
return
var bscript: Script = vb.get_script()
var sky_pfad: String = bscript.panorama_sky_pfad()
_check(not sky_pfad.is_empty(), "T3 Client-Nachbar-Klon gefunden (%s)" % sky_pfad)
if sky_pfad.is_empty() or not FileAccess.file_exists(sky_pfad):
print(" (Pack-Skybox im Nachbar-Klon nicht erreichbar — Panorama-Checks übersprungen)")
return
main.doc["weather_sky"] = "sky_panorama"
vb.call("rebuild", main.doc, main.manifest)
await process_frame
var we := _welt_env(vb)
_check(we != null, "T3 WorldEnvironment vorhanden")
if we != null and we.environment != null and we.environment.sky != null:
_check(we.environment.sky.sky_material is PanoramaSkyMaterial,
"T3 weather_sky=sky_panorama → PanoramaSkyMaterial in der Vorschau")
_check(we.environment.tonemap_mode == Environment.TONE_MAPPER_ACES,
"T3 Vorschau tonemappt ACES")
var wl: Dictionary = bscript.get_script_constant_map().get("WEATHER_LIGHT", {})
var preset: Dictionary = wl.get(str(main.doc.get("weather", "mittag")), {})
if preset.has("panorama_fog_color"):
_check((we.environment.fog_light_color as Color).is_equal_approx(
preset["panorama_fog_color"]),
"T3 Fog am Skybox-Horizont (panorama_fog_color)")
# Feld entfernen → Procedural (alte Maps laden unverändert).
main.doc.erase("weather_sky")
vb.call("rebuild", main.doc, main.manifest)
await process_frame
var we2 := _welt_env(vb)
_check(we2 != null and we2.environment != null and we2.environment.sky != null
and we2.environment.sky.sky_material is ProceduralSkyMaterial,
"T3 ohne weather_sky → ProceduralSkyMaterial (alte Maps unverändert)")
## Neue Skyboxen: Auswahl → doc + Vorschau-Bild + Viewport-Sky; Undo/Redo.
func _t4_auswahl_undo() -> void:
print("== T4: Skybox-Auswahl mit Undo/Redo ==")
var prop_sky: OptionButton = map_panel.get("prop_sky")
var vb = main.view_builder
main.doc.erase("weather_sky")
map_panel.call("refresh_values")
var idx := -1
for i in SkyboxKatalog.EINTRAEGE.size():
if SkyboxKatalog.EINTRAEGE[i]["id"] == "sky_98":
idx = i + 1
_check(idx > 0, "T4 sky_98 im Katalog")
var h = main.history
var n0: int = h.entries.size()
prop_sky.select(idx)
map_panel.call("_on_sky_changed", idx)
_check(str(main.doc.get("weather_sky", "")) == "sky_98", "T4 Auswahl schreibt weather_sky=sky_98")
_check(h.entries.size() == n0 + 1, "T4 Auswahl landet in der Undo-Historie")
var prev: TextureRect = map_panel.get("sky_preview")
var tex_ok := SkyboxKatalog.textur_pfad("sky_98") != "" and FileAccess.file_exists(SkyboxKatalog.textur_pfad("sky_98"))
if tex_ok:
_check(prev.texture != null and prev.visible, "T4 Vorschau-Bild sichtbar")
await process_frame
var we := _welt_env(vb)
_check(we != null and we.environment.sky.sky_material is PanoramaSkyMaterial,
"T4 Viewport zeigt Panorama (sky_98)")
# Gleiche Auswahl nochmal = kein neuer Historien-Eintrag.
map_panel.call("_on_sky_changed", idx)
_check(h.entries.size() == n0 + 1, "T4 gleiche Wahl erzeugt keinen Eintrag")
# Undo → Feld weg, UI zurück auf Standard.
main.call("_do_undo")
_check(not main.doc.has("weather_sky"), "T4 Undo entfernt weather_sky")
_check(prop_sky.selected == 0, "T4 Undo: Auswahlfeld zeigt Standard")
main.call("_do_redo")
_check(str(main.doc.get("weather_sky", "")) == "sky_98", "T4 Redo setzt sky_98 wieder")
_check(prop_sky.selected == idx, "T4 Redo: Auswahlfeld zeigt sky_98")
# Wechsel auf anderen Himmel, Undo → zurück auf sky_98 (nicht Standard).
map_panel.call("_on_sky_changed", 2)
main.call("_do_undo")
_check(str(main.doc.get("weather_sky", "")) == "sky_98", "T4 Undo nach Wechsel → vorheriger Himmel")
# Aufräumen.
map_panel.call("_on_sky_changed", 0)
_check(not main.doc.has("weather_sky"), "T4 Aufräumen")
# Katalog-Vertrag: Server-Feld überlebt Speichern/Laden.
var d: Dictionary = main.doc.duplicate(true)
d["weather_sky"] = "sky_14"
SvmapIO.sign_map(d)
_check(SvmapIO.validate_map(d).is_empty(), "T4 Map mit sky_14 signiert + valide")
func _welt_env(vb) -> WorldEnvironment:
for child in vb.get_children():
if child is WorldEnvironment:
return child
return null
func _finish() -> void:
print("--------------------------------------------------")
print("CHECKS: %d FAILURES: %d" % [checks, failures])
quit(0 if failures == 0 else 1)