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)