Atmosphaere & Skybox (t_f72913d4): Map-Option „Himmel: Pack-Skybox“ (weather_sky=sky_panorama, checksum-neutral), Vorschau = 1:1-Spiegel Client-Wetter (ACES/Fog/sun_deg/Panorama via Nachbar-Klon), V17-Validator; Suite test_skybox 23/23
This commit is contained in:
parent
35a1d80d3e
commit
0505463ace
5 changed files with 325 additions and 19 deletions
|
|
@ -28,6 +28,11 @@ const SUPPORTED_FORMAT_VERSIONS: Array[int] = [1, 2]
|
||||||
## Material-IDs (Index = ID). 0 = Gras = Default für Maps ohne Schicht.
|
## Material-IDs (Index = ID). 0 = Gras = Default für Maps ohne Schicht.
|
||||||
const MATERIAL_IDS: Array[String] = ["gras", "sand", "fels"]
|
const MATERIAL_IDS: Array[String] = ["gras", "sand", "fels"]
|
||||||
const WEATHER_PRESETS: Array[String] = ["mittag", "abend", "regen", "nacht"]
|
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).
|
# Perf-/Plausibilitäts-Limits — identisch zum Server-Loader (docs/map_format.md §4).
|
||||||
const MAX_FILE_BYTES := 50 * 1024 * 1024 # V1
|
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):
|
if not (weather is String) or not WEATHER_PRESETS.has(weather):
|
||||||
errors.append("V12: weather '%s' ist kein gültiges Preset (%s)." % [
|
errors.append("V12: weather '%s' ist kein gültiges Preset (%s)." % [
|
||||||
str(weather), ", ".join(WEATHER_PRESETS)])
|
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
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ var prop_author: LineEdit
|
||||||
var prop_desc: LineEdit
|
var prop_desc: LineEdit
|
||||||
var prop_version: SpinBox
|
var prop_version: SpinBox
|
||||||
var prop_weather: OptionButton
|
var prop_weather: OptionButton
|
||||||
|
var prop_sky: OptionButton
|
||||||
var terrain_info: Label
|
var terrain_info: Label
|
||||||
|
|
||||||
var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder
|
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)")
|
prop_weather.add_item("Nacht (dunkel)")
|
||||||
add_child(prop_weather)
|
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(_separator())
|
||||||
add_child(_header_label("Terrain"))
|
add_child(_header_label("Terrain"))
|
||||||
terrain_info = Label.new()
|
terrain_info = Label.new()
|
||||||
|
|
@ -70,6 +79,7 @@ func build() -> void:
|
||||||
prop_desc.text_changed.connect(_on_prop_changed.bind("description"))
|
prop_desc.text_changed.connect(_on_prop_changed.bind("description"))
|
||||||
prop_version.value_changed.connect(_on_version_changed)
|
prop_version.value_changed.connect(_on_version_changed)
|
||||||
prop_weather.item_selected.connect(_on_weather_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:
|
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])
|
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).
|
## Felder aus dem doc auffüllen (aus main.gd _refresh_all-Anfang ausgezogen).
|
||||||
func refresh_values() -> void:
|
func refresh_values() -> void:
|
||||||
_loading_ui_values = true
|
_loading_ui_values = true
|
||||||
|
|
@ -119,6 +148,8 @@ func refresh_values() -> void:
|
||||||
var weathers := ["mittag", "abend", "regen", "nacht"]
|
var weathers := ["mittag", "abend", "regen", "nacht"]
|
||||||
var weather := str(main.doc.get("weather", "mittag"))
|
var weather := str(main.doc.get("weather", "mittag"))
|
||||||
prop_weather.select(maxi(0, weathers.find(weather)))
|
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", {})
|
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" % [
|
terrain_info.text = "Größe: %.0f × %.0f m\nAuflösung: %d × %d Höhenpunkte\nHöhenbereich: %.0f bis %.0f m" % [
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,70 @@ const OUTLINE_SHADER := "res://shaders/selection_outline.gdshader"
|
||||||
# Fallback, falls das Theme (noch) nicht geladen werden kann.
|
# Fallback, falls das Theme (noch) nicht geladen werden kann.
|
||||||
const OUTLINE_FALLBACK := Color(1.0, 0.6, 0.15, 0.95)
|
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 := {
|
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)},
|
"mittag": {
|
||||||
"abend": {"energy": 0.9, "color": Color(1.0, 0.75, 0.5), "ambient": 0.3, "sky_top": Color(0.35, 0.3, 0.55)},
|
"sky_top": Color(0.30, 0.54, 0.86), "sky_horizon": Color(0.72, 0.83, 0.92),
|
||||||
"regen": {"energy": 0.6, "color": Color(0.8, 0.85, 0.9), "ambient": 0.35, "sky_top": Color(0.45, 0.48, 0.52)},
|
"ground_bottom": Color(0.20, 0.23, 0.27), "ground_horizon": Color(0.66, 0.75, 0.83),
|
||||||
"nacht": {"energy": 0.25, "color": Color(0.6, 0.7, 1.0), "ambient": 0.12, "sky_top": Color(0.03, 0.05, 0.12)},
|
"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_index := -1 # aktuelles Auswahl-Objekt (Kontur-Markierung)
|
||||||
var selected_indices: Array = [] # komplette Auswahl (Karte t_f6f29d86 — Mehrfachauswahl)
|
var selected_indices: Array = [] # komplette Auswahl (Karte t_f6f29d86 — Mehrfachauswahl)
|
||||||
var hover_index := -1 # Hover-Objekt (schwächste Kontur; U6-Abstufung)
|
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 size_z := float(terrain.get("size_z_m", 1024.0))
|
||||||
var ground_y := float(terrain.get("min_height_m", 0.0))
|
var ground_y := float(terrain.get("min_height_m", 0.0))
|
||||||
var weather := str(data.get("weather", "mittag"))
|
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_terrain(data)
|
||||||
_build_grid(size_x, size_z, ground_y)
|
_build_grid(size_x, size_z, ground_y)
|
||||||
# Gitter default AUS über sichtbarem Terrain (Karte t_7ac70014): das
|
# 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)
|
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 preset: Dictionary = WEATHER_LIGHT.get(weather, WEATHER_LIGHT["mittag"])
|
||||||
var env := Environment.new()
|
var env := Environment.new()
|
||||||
var sky := Sky.new()
|
var sky := Sky.new()
|
||||||
|
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()
|
var sky_mat := ProceduralSkyMaterial.new()
|
||||||
sky_mat.sky_top_color = preset["sky_top"]
|
sky_mat.sky_top_color = preset["sky_top"]
|
||||||
sky_mat.sky_horizon_color = Color(0.6, 0.65, 0.7)
|
sky_mat.sky_horizon_color = preset["sky_horizon"]
|
||||||
sky_mat.ground_bottom_color = Color(0.1, 0.1, 0.12)
|
sky_mat.ground_bottom_color = preset["ground_bottom"]
|
||||||
sky_mat.ground_horizon_color = Color(0.4, 0.42, 0.45)
|
sky_mat.ground_horizon_color = preset["ground_horizon"]
|
||||||
|
sky_mat.sky_energy_multiplier = float(preset["sky_energy"])
|
||||||
sky.sky_material = sky_mat
|
sky.sky_material = sky_mat
|
||||||
env.background_mode = Environment.BG_SKY
|
env.background_mode = Environment.BG_SKY
|
||||||
env.sky = sky
|
env.sky = sky
|
||||||
|
# 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_source = Environment.AMBIENT_SOURCE_COLOR
|
||||||
env.ambient_light_color = Color(1, 1, 1)
|
env.ambient_light_color = preset["ambient_color"]
|
||||||
env.ambient_light_energy = preset["ambient"]
|
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()
|
var we := WorldEnvironment.new()
|
||||||
we.environment = env
|
we.environment = env
|
||||||
add_child(we)
|
add_child(we)
|
||||||
|
|
||||||
var sun := DirectionalLight3D.new()
|
var sun := DirectionalLight3D.new()
|
||||||
sun.rotation_degrees = Vector3(-50.0, 30.0, 0.0)
|
var deg: Vector2 = preset["sun_deg"]
|
||||||
sun.light_energy = preset["energy"]
|
sun.rotation_degrees = Vector3(deg.x, deg.y, 0.0)
|
||||||
sun.light_color = preset["color"]
|
sun.light_energy = float(preset["sun_energy"])
|
||||||
|
sun.light_color = preset["sun_color"]
|
||||||
sun.shadow_enabled = true
|
sun.shadow_enabled = true
|
||||||
add_child(sun)
|
add_child(sun)
|
||||||
|
|
||||||
|
|
|
||||||
160
tests/test_skybox.gd
Normal file
160
tests/test_skybox.gd
Normal file
|
|
@ -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)
|
||||||
1
tests/test_skybox.gd.uid
Normal file
1
tests/test_skybox.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://caxg45x2wi8w5
|
||||||
Loading…
Reference in a new issue