From 0505463ace69858e554de619f85a4d7fdf007dc3 Mon Sep 17 00:00:00 2001 From: "Hermes (scopedvr-dev)" Date: Tue, 20 Oct 2026 22:10:36 +0000 Subject: [PATCH] =?UTF-8?q?Atmosphaere=20&=20Skybox=20(t=5Ff72913d4):=20Ma?= =?UTF-8?q?p-Option=20=E2=80=9EHimmel:=20Pack-Skybox=E2=80=9C=20(weather?= =?UTF-8?q?=5Fsky=3Dsky=5Fpanorama,=20checksum-neutral),=20Vorschau=20=3D?= =?UTF-8?q?=201:1-Spiegel=20Client-Wetter=20(ACES/Fog/sun=5Fdeg/Panorama?= =?UTF-8?q?=20via=20Nachbar-Klon),=20V17-Validator;=20Suite=20test=5Fskybo?= =?UTF-8?q?x=2023/23?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/svmap/svmap_io.gd | 14 +++ src/ui/panels/map_panel.gd | 31 ++++++ src/viewport/map_view_builder.gd | 138 ++++++++++++++++++++++---- tests/test_skybox.gd | 160 +++++++++++++++++++++++++++++++ tests/test_skybox.gd.uid | 1 + 5 files changed, 325 insertions(+), 19 deletions(-) create mode 100644 tests/test_skybox.gd create mode 100644 tests/test_skybox.gd.uid diff --git a/src/svmap/svmap_io.gd b/src/svmap/svmap_io.gd index 308c9d1..bc4edd5 100644 --- a/src/svmap/svmap_io.gd +++ b/src/svmap/svmap_io.gd @@ -28,6 +28,11 @@ const SUPPORTED_FORMAT_VERSIONS: Array[int] = [1, 2] ## Material-IDs (Index = ID). 0 = Gras = Default für Maps ohne Schicht. const MATERIAL_IDS: Array[String] = ["gras", "sand", "fels"] const WEATHER_PRESETS: Array[String] = ["mittag", "abend", "regen", "nacht"] +## Skybox-Option (Karte t_f72913d4): Werte des optionalen Feldes `weather_sky` +## — identische Semantik wie der Client (Weather.apply_preset sky_override): +## "sky_panorama" = Pack-Panorama erzwingen, "prozedural" = ProceduralSky +## erzwingen, leer/fehlend = Preset entscheidet (in allen Presets aus). +const WEATHER_SKY_OPTIONS: Array[String] = ["sky_panorama", "prozedural"] # Perf-/Plausibilitäts-Limits — identisch zum Server-Loader (docs/map_format.md §4). const MAX_FILE_BYTES := 50 * 1024 * 1024 # V1 @@ -171,6 +176,15 @@ static func _validate_core(data: Dictionary, manifest: Dictionary = {}) -> Packe if not (weather is String) or not WEATHER_PRESETS.has(weather): errors.append("V12: weather '%s' ist kein gültiges Preset (%s)." % [ str(weather), ", ".join(WEATHER_PRESETS)]) + # V17: weather_sky (optional, Karte t_f72913d4) — leer/fehlend = Standard. + # Checksum-neutral wie tint/gebaeude: der Server kennt das Feld nicht, die + # Prüfsumme läuft über die ganze Payload — unbekannte Zusatzfelder stören + # die Server-Validierung nicht, nur WERT-Fehler fangen wir hier ab. + var weather_sky = data.get("weather_sky", "") + if weather_sky != null and str(weather_sky) != "": + if not (weather_sky is String) or not WEATHER_SKY_OPTIONS.has(weather_sky): + errors.append("V17: weather_sky '%s' ist keine gültige Option (%s)." % [ + str(weather_sky), ", ".join(WEATHER_SKY_OPTIONS)]) return errors diff --git a/src/ui/panels/map_panel.gd b/src/ui/panels/map_panel.gd index d2bdd53..00aec5e 100644 --- a/src/ui/panels/map_panel.gd +++ b/src/ui/panels/map_panel.gd @@ -10,6 +10,7 @@ var prop_author: LineEdit var prop_desc: LineEdit var prop_version: SpinBox var prop_weather: OptionButton +var prop_sky: OptionButton var terrain_info: Label var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder @@ -57,6 +58,14 @@ func build() -> void: prop_weather.add_item("Nacht (dunkel)") add_child(prop_weather) + add_child(_field_label("Himmel")) + prop_sky = OptionButton.new() + # Karte Atmosphäre/Skybox: Panorama-Skybox aus dem Pack als Map-Option. + prop_sky.tooltip_text = "Himmel: Prozedural (Standard) oder Pack-Skybox (Wüstenpanorama). „Standard“ lässt die Karte unverändert." + prop_sky.add_item("Prozedural (Standard)") + prop_sky.add_item("Pack-Skybox (Wüste)") + add_child(prop_sky) + add_child(_separator()) add_child(_header_label("Terrain")) terrain_info = Label.new() @@ -70,6 +79,7 @@ func build() -> void: prop_desc.text_changed.connect(_on_prop_changed.bind("description")) prop_version.value_changed.connect(_on_version_changed) prop_weather.item_selected.connect(_on_weather_changed) + prop_sky.item_selected.connect(_on_sky_changed) func _on_prop_changed(text: String, field: String) -> void: @@ -108,6 +118,25 @@ func _on_weather_changed(index: int) -> void: main.call("_set_status", "Wetter geändert auf „%s“." % weathers[index]) +## Karte Atmosphäre/Skybox: Himmel „Pack-Skybox (Wüste)“ → doc["weather_sky"] +## = "sky_panorama" (ein Speicherfeld, checksum-neutral wie tint/gebaeude; +## Wert = der Preset-Key `sky_panorama`, den der Client beim Laden erzwingt). +## „Prozedural (Standard)“ = Index 0 → Feld WIEDER ENTFERNEN (alte Maps +## unverändert — kein "prozedural"-Wert im doc). Sofort-Neuaufbau wie Wetter. +func _on_sky_changed(index: int) -> void: + if _loading_ui_values or main.doc.is_empty(): + return + if index == 1: + main.doc["weather_sky"] = "sky_panorama" + else: + main.doc.erase("weather_sky") + main.dirty = true + main.call("_refresh_file_label") + main.view_builder.call("rebuild", main.doc, main.manifest) + main.call("_sync_grid_to_builder") + main.call("_set_status", "Himmel geändert auf „%s“." % ["Prozedural (Standard)", "Pack-Skybox (Wüste)"][index]) + + ## Felder aus dem doc auffüllen (aus main.gd _refresh_all-Anfang ausgezogen). func refresh_values() -> void: _loading_ui_values = true @@ -119,6 +148,8 @@ func refresh_values() -> void: var weathers := ["mittag", "abend", "regen", "nacht"] var weather := str(main.doc.get("weather", "mittag")) prop_weather.select(maxi(0, weathers.find(weather))) + # Himmel: Pack-Skybox ausgewählt ↔ doc["weather_sky"] == "sky_panorama". + prop_sky.select(1 if str(main.doc.get("weather_sky", "")) == "sky_panorama" else 0) var terrain: Dictionary = main.doc.get("terrain", {}) terrain_info.text = "Größe: %.0f × %.0f m\nAuflösung: %d × %d Höhenpunkte\nHöhenbereich: %.0f bis %.0f m" % [ diff --git a/src/viewport/map_view_builder.gd b/src/viewport/map_view_builder.gd index 3527ecd..665fbce 100644 --- a/src/viewport/map_view_builder.gd +++ b/src/viewport/map_view_builder.gd @@ -21,14 +21,70 @@ const OUTLINE_SHADER := "res://shaders/selection_outline.gdshader" # Fallback, falls das Theme (noch) nicht geladen werden kann. const OUTLINE_FALLBACK := Color(1.0, 0.6, 0.15, 0.95) -# Wetter → Licht (grobe Stimmung; Feintuning kommt mit dem Wetter-Konsum) +# Wetter → Licht/Himmel — 1:1-SPIEGEL aus client/scripts/world/weather_presets.gd +# (Karte t_f72913d4: Editor-Vorschau zeigt exakt was der Client zeigt — dieselbe +# Projekt-Konvention wie KolliderBau als 1:1-Spiegel von svmap_world, t_1da96275). +# Bei Preset-Änderungen im Client HIER MITPFLEGEN. Schlüssel = Client-Keys. const WEATHER_LIGHT := { - "mittag": {"energy": 1.2, "color": Color(1.0, 0.98, 0.9), "ambient": 0.45, "sky_top": Color(0.28, 0.5, 0.85)}, - "abend": {"energy": 0.9, "color": Color(1.0, 0.75, 0.5), "ambient": 0.3, "sky_top": Color(0.35, 0.3, 0.55)}, - "regen": {"energy": 0.6, "color": Color(0.8, 0.85, 0.9), "ambient": 0.35, "sky_top": Color(0.45, 0.48, 0.52)}, - "nacht": {"energy": 0.25, "color": Color(0.6, 0.7, 1.0), "ambient": 0.12, "sky_top": Color(0.03, 0.05, 0.12)}, + "mittag": { + "sky_top": Color(0.30, 0.54, 0.86), "sky_horizon": Color(0.72, 0.83, 0.92), + "ground_bottom": Color(0.20, 0.23, 0.27), "ground_horizon": Color(0.66, 0.75, 0.83), + "sky_energy": 1.0, + "sun_deg": Vector2(-52.0, 30.0), "sun_energy": 1.2, "sun_color": Color(1.0, 0.98, 0.92), + "ambient_source": "sky", "ambient_color": Color(0.60, 0.70, 0.85), "ambient_energy": 1.0, + "fog_enabled": false, "fog_color": Color(0.80, 0.85, 0.90), + "fog_density": 0.0, "fog_sky_affect": 0.0, + "sky_panorama": false, + "panorama_fog_color": Color(0.93, 0.80, 0.66), "panorama_energy": 1.0, + }, + "abend": { + "sky_top": Color(0.25, 0.30, 0.55), "sky_horizon": Color(0.95, 0.55, 0.28), + "ground_bottom": Color(0.16, 0.13, 0.14), "ground_horizon": Color(0.80, 0.45, 0.25), + "sky_energy": 1.0, + "sun_deg": Vector2(-8.0, 250.0), "sun_energy": 0.9, "sun_color": Color(1.0, 0.62, 0.35), + "ambient_source": "color", "ambient_color": Color(0.55, 0.40, 0.32), "ambient_energy": 0.8, + "fog_enabled": true, "fog_color": Color(0.85, 0.55, 0.35), + "fog_density": 0.004, "fog_sky_affect": 0.2, + "sky_panorama": false, + "panorama_fog_color": Color(0.95, 0.72, 0.48), "panorama_energy": 0.8, + }, + "regen": { + "sky_top": Color(0.38, 0.41, 0.45), "sky_horizon": Color(0.55, 0.58, 0.62), + "ground_bottom": Color(0.22, 0.23, 0.25), "ground_horizon": Color(0.50, 0.53, 0.57), + "sky_energy": 0.9, + "sun_deg": Vector2(-40.0, 120.0), "sun_energy": 0.25, "sun_color": Color(0.75, 0.78, 0.82), + "ambient_source": "color", "ambient_color": Color(0.55, 0.58, 0.62), "ambient_energy": 1.0, + "fog_enabled": true, "fog_color": Color(0.55, 0.58, 0.62), + "fog_density": 0.015, "fog_sky_affect": 0.6, + "sky_panorama": false, + "panorama_fog_color": Color(0.76, 0.72, 0.68), "panorama_energy": 0.55, + }, + "nacht": { + "sky_top": Color(0.015, 0.03, 0.08), "sky_horizon": Color(0.05, 0.08, 0.15), + "ground_bottom": Color(0.01, 0.015, 0.025), "ground_horizon": Color(0.04, 0.06, 0.11), + "sky_energy": 1.0, + "sun_deg": Vector2(-65.0, 200.0), "sun_energy": 0.35, "sun_color": Color(0.62, 0.72, 1.0), + "ambient_source": "color", "ambient_color": Color(0.10, 0.14, 0.25), "ambient_energy": 0.5, + "fog_enabled": true, "fog_color": Color(0.04, 0.06, 0.11), + "fog_density": 0.006, "fog_sky_affect": 0.4, + "sky_panorama": false, + "panorama_fog_color": Color(0.10, 0.12, 0.18), "panorama_energy": 0.10, + }, } +## Pack-Panorama-Skybox (Karte t_f72913d4): die Textur liegt im Client-Repo +## (Nachbar-Klon — KEINE Duplikat-Pflicht im Editor, siehe Spec) und wird über +## einen absoluten Pfad geladen; fehlt sie → Fallback ProceduralSky. +const PANORAMA_SKY_CLIENT_REL := "assets/textures/military_desert/Skybox_3.png" + +## Absoluter Pfad der Pack-Skybox im Nachbar-Klon ("" = kein Klon gefunden). +static func panorama_sky_pfad() -> String: + var root := TestRun.find_client(ProjectSettings.globalize_path("res://"), + TestRun.load_cfg()) + if root.is_empty(): + return "" + return root.path_join(PANORAMA_SKY_CLIENT_REL) + var selected_index := -1 # aktuelles Auswahl-Objekt (Kontur-Markierung) var selected_indices: Array = [] # komplette Auswahl (Karte t_f6f29d86 — Mehrfachauswahl) var hover_index := -1 # Hover-Objekt (schwächste Kontur; U6-Abstufung) @@ -84,8 +140,11 @@ func rebuild(data: Dictionary, manifest: Dictionary) -> void: var size_z := float(terrain.get("size_z_m", 1024.0)) var ground_y := float(terrain.get("min_height_m", 0.0)) var weather := str(data.get("weather", "mittag")) + # Skybox-Option (Karte t_f72913d4): optionales svmap-Feld `weather_sky` + # ("sky_panorama"/"prozedural"/leer) — dieselbe Semantik wie im Client. + var weather_sky := str(data.get("weather_sky", "")) - _build_environment(weather) + _build_environment(weather, weather_sky) _build_terrain(data) _build_grid(size_x, size_z, ground_y) # Gitter default AUS über sichtbarem Terrain (Karte t_7ac70014): das @@ -125,29 +184,70 @@ func rebuild_terrain_from_doc(data: Dictionary) -> void: terrain_view.call("rebuild_from_doc", data) -func _build_environment(weather: String) -> void: +## Himmel/Licht — 1:1-Spiegel von client autoload/weather.gd._apply_environment +## (Karte t_f72913d4): Map-Override > Preset-Key > ProceduralSky; ACES-Tonemap; +## Fog koppelt im Panorama-Fall an den Skybox-Horizont (aerial perspective). +## Kein Glow/Bloom/Volumetric — dokumentierte Absage (Mobile/Perf). +func _build_environment(weather: String, sky_override: String = "") -> void: var preset: Dictionary = WEATHER_LIGHT.get(weather, WEATHER_LIGHT["mittag"]) var env := Environment.new() var sky := Sky.new() - var sky_mat := ProceduralSkyMaterial.new() - sky_mat.sky_top_color = preset["sky_top"] - sky_mat.sky_horizon_color = Color(0.6, 0.65, 0.7) - sky_mat.ground_bottom_color = Color(0.1, 0.1, 0.12) - sky_mat.ground_horizon_color = Color(0.4, 0.42, 0.45) - sky.sky_material = sky_mat + var panorama := false + if sky_override == "prozedural": + panorama = false + elif sky_override == "sky_panorama": + panorama = true + else: + panorama = bool(preset.get("sky_panorama", false)) + if panorama: + var pfad := panorama_sky_pfad() + if not pfad.is_empty() and FileAccess.file_exists(pfad): + var img := Image.load_from_file(pfad) + if img != null: + if img.is_compressed(): + img.decompress() + img.generate_mipmaps() + var pano_mat := PanoramaSkyMaterial.new() + pano_mat.panorama = ImageTexture.create_from_image(img) + pano_mat.energy_multiplier = float(preset.get("panorama_energy", 1.0)) + sky.sky_material = pano_mat + else: + push_warning("MAPBUILDER: Panorama-Textur nicht ladbar (%s) — Fallback ProceduralSky" % pfad) + else: + push_warning("MAPBUILDER: weather_sky=sky_panorama, aber Pack-Skybox fehlt (%s) — Fallback ProceduralSky" % pfad) + if sky.sky_material == null: + var sky_mat := ProceduralSkyMaterial.new() + sky_mat.sky_top_color = preset["sky_top"] + sky_mat.sky_horizon_color = preset["sky_horizon"] + sky_mat.ground_bottom_color = preset["ground_bottom"] + sky_mat.ground_horizon_color = preset["ground_horizon"] + sky_mat.sky_energy_multiplier = float(preset["sky_energy"]) + sky.sky_material = sky_mat env.background_mode = Environment.BG_SKY env.sky = sky - env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR - env.ambient_light_color = Color(1, 1, 1) - env.ambient_light_energy = preset["ambient"] + # Ambient: "sky" = aus dem Himmel ableiten, "color" = feste Farbe (Client-Logik). + if str(preset.get("ambient_source", "color")) == "sky": + env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY + else: + env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR + env.ambient_light_color = preset["ambient_color"] + env.ambient_light_energy = float(preset["ambient_energy"]) + # Tonemap ACES — derselbe filmische Look wie der Client; Grading bewusst + # NUR über Tonemap + Preset-Farben (kein Fullscreen-Adjustment-Pass). + env.tonemap_mode = Environment.TONE_MAPPER_ACES + env.fog_enabled = bool(preset["fog_enabled"]) + env.fog_light_color = preset.get("panorama_fog_color", preset["fog_color"]) if panorama else preset["fog_color"] + env.fog_density = float(preset["fog_density"]) + env.fog_sky_affect = float(preset["fog_sky_affect"]) var we := WorldEnvironment.new() we.environment = env add_child(we) var sun := DirectionalLight3D.new() - sun.rotation_degrees = Vector3(-50.0, 30.0, 0.0) - sun.light_energy = preset["energy"] - sun.light_color = preset["color"] + var deg: Vector2 = preset["sun_deg"] + sun.rotation_degrees = Vector3(deg.x, deg.y, 0.0) + sun.light_energy = float(preset["sun_energy"]) + sun.light_color = preset["sun_color"] sun.shadow_enabled = true add_child(sun) diff --git a/tests/test_skybox.gd b/tests/test_skybox.gd new file mode 100644 index 0000000..748b4ff --- /dev/null +++ b/tests/test_skybox.gd @@ -0,0 +1,160 @@ +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() + _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"]: + 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", 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 == 2, "T2 Himmel-Optionen: 2 Einträge (ist %d)" % prop_sky.item_count) + _check(str(prop_sky.get_item_text(1)).contains("Pack-Skybox"), + "T2 Eintrag 2 = Pack-Skybox (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)") + + +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) diff --git a/tests/test_skybox.gd.uid b/tests/test_skybox.gd.uid new file mode 100644 index 0000000..f8e3c01 --- /dev/null +++ b/tests/test_skybox.gd.uid @@ -0,0 +1 @@ +uid://caxg45x2wi8w5