scopedvr-map-editor/tests/test_wasser.gd
2026-11-01 19:34:05 +01:00

202 lines
8.6 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-Test (headless): Wasser pro Map (Karte t_28fdef03).
## Ausführen: godot --headless --path . --script res://tests/test_wasser.gd
## Exit 0 = alles grün.
##
## W1 Format (SvmapIO V20): water_level optional; gültige Zahl innerhalb
## des Terrain-Höhenbereichs ±10 m → keine Fehler; String/INF/draußen →
## V20-Fehler; Legacy (ohne Feld) validiert unverändert.
## W2 Map-Panel „Wasserhöhe“: Regler existiert, doc-Roundtrip
## (slider.value = Wasserhöhe; „Kein Wasser“ = Feld ENTFERNEN;
## refresh_values füllt aus dem doc auf; Slider-Nutzung markiert dirty).
## W3 Editor-Vorschau (map_view_builder): doc mit water_level →
## Wasser/WasserFlaeche existiert, liegt exakt auf der Höhe, Material =
## dasselbe Shader-Verfahren wie der Client (SvmapWasser.make_material);
## ohne Feld → kein Wasser-Node (alte Maps unverändert); Matsch-Ring
## in den Terrain-Zellfarben (Ufer-Automatik, gleiche Formel wie Client).
## W4 Validator-Spiegel: V20-Band/-Grenzen identisch zum Match-Server.
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://core/scenes/main.tscn")
_check(scene != null, "Aufbau: 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, "Aufbau: Map-Panel in main eingebaut")
_check(main.get("terrain_mod") != null, "Aufbau: Terrain-Panel eingebaut")
_check(main.view_builder != null, "Aufbau: view_builder vorhanden")
if main == null or map_panel == null or main.view_builder == null:
_finish()
return
await _w1_validierung()
await _w2_panel()
await _w3_vorschau()
_w4_validator_spiegel()
_finish()
func _w1_validierung() -> void:
print("== W1: svmap-Validierung water_level (V20) ==")
var terrain: Dictionary = main.doc["terrain"]
var hmin := float(terrain["min_height_m"])
var hmax := float(terrain["max_height_m"])
# Legacy ohne Feld: unverändert gültig.
var alte: Dictionary = main.doc.duplicate(true)
alte.erase("water_level")
SvmapIO.sign_map(alte)
var f0: PackedStringArray = SvmapIO.validate_map(alte)
_check(f0.is_empty(), "W1 Karte ohne water_level validiert (%s)" % str(f0))
# Gültige Werte.
for wert in [hmin, hmax, (hmin + hmax) * 0.5]:
var d: Dictionary = main.doc.duplicate(true)
(d["terrain"] as Dictionary)["water_level"] = wert
SvmapIO.sign_map(d)
var f: PackedStringArray = SvmapIO.validate_map(d)
_check(f.is_empty(), "W1 water_level=%.1f (Höhenbereich) validiert (%s)" % [wert, str(f)])
# Ungültige Werte → V20 (_validate_core, ohne Prüfsumme).
for wert in ["hoch", INF, hmax + 10.5, hmin - 10.5]:
var d2: Dictionary = main.doc.duplicate(true)
(d2["terrain"] as Dictionary)["water_level"] = wert
var f2: PackedStringArray = SvmapIO._validate_core(d2)
var hat_v20 := false
for e in f2:
if str(e).begins_with("V20"):
hat_v20 = true
_check(hat_v20, "W1 water_level=%s → V20-Fehler (%s)" % [str(wert), str(f2)])
func _w2_panel() -> void:
print("== W2: Map-Panel Wasserhöhe ==")
var slider: HSlider = map_panel.get("prop_water")
_check(slider != null, "W2 prop_water (Regler) existiert")
if slider == null:
return
_check(is_equal_approx(float(slider.get_meta("kein_wasser_value", 0.0)), -10.0),
"W2 Slider trägt die Kein-Wasser-Kennung (−10)")
# doc OHNE Feld → Regler auf "Kein Wasser".
main.doc.erase("water_level")
map_panel.call("refresh_values")
_check(is_equal_approx(float(slider.value), -10.0),
"W2 ohne Feld → Regler auf „Kein Wasser“")
# doc MIT Feld → Regler zeigt die Höhe.
main.doc["water_level"] = 3.5
map_panel.call("refresh_values")
_check(is_equal_approx(float(slider.value), 3.5), "W2 doc=3.5 → Regler 3.5")
# Regler bewegen → doc + dirty. Erst das dirty-Flag aus dem refresh löschen.
main.set("dirty", false)
slider.value_changed.emit(4.25)
_check(bool(main.get("dirty")), "W2 Regler-Bewegung markiert dirty")
_check(is_equal_approx(float(main.doc.get("water_level", -999.0)), 4.25),
"W2 Regler schreibt water_level ins doc")
# Regler zurück auf „Kein Wasser“ → Feld ENTFERNEN (Legacy-sauber).
slider.set_value_no_signal(-10.0)
slider.value_changed.emit(-10.0)
_check(not main.doc.has("water_level"), "W2 „Kein Wasser“ entfernt das Feld")
_check(bool(main.get("dirty")), "W2 Entfernen markiert ebenfalls dirty")
# Für W3: Feld wieder setzen (per Panel-Handler — dieselbe Verdrahtung).
slider.set_value_no_signal(2.0)
slider.value_changed.emit(2.0)
_check(is_equal_approx(float(main.doc.get("water_level", -999.0)), 2.0),
"W2 Feld wieder gesetzt (2.0) für die Vorschau-Prüfung")
func _w3_vorschau() -> void:
print("== W3: Editor-Vorschau (map_view_builder) ==")
var vb = main.view_builder
# Terrain mit einer echten Senke bauen, damit Wasser sichtbar ist.
var terrain: Dictionary = main.doc["terrain"]
var res := int(terrain["resolution"])
var hmin := float(terrain["min_height_m"])
var heights: PackedFloat32Array = SvmapIO.decode_heights(terrain)
for i in heights.size():
heights[i] = hmin
# Senke in der Map-Mitte unter die Wasserhöhe (2.0) graben.
var mitte := int(float(res) * 0.5)
for z in range(maxi(mitte - 4, 0), mini(mitte + 5, res)):
for x in range(maxi(mitte - 4, 0), mini(mitte + 5, res)):
heights[z * res + x] = 2.0 - 1.5
terrain["heightmap_r16_b64"] = TerrainField.encode(heights, hmin, float(terrain["max_height_m"]))
SvmapIO.sign_map(main.doc)
await process_frame
await process_frame # queue_free der alten Kinder aus den W2-Rebuilds
vb.call("rebuild", main.doc, main.manifest)
await process_frame
await process_frame # queue_free der alten Kinder braucht 2 Frames
var wasser: Node3D = _wasser_node(vb)
_check(wasser != null, "W3 Wasser-Node in der Vorschau (Senke gefüllt)")
if wasser == null:
return
var mi := wasser.get_node_or_null("WasserFlaeche") as MeshInstance3D
_check(mi != null, "W3 WasserFlaeche existiert")
if mi != null:
_check(is_equal_approx(mi.position.y, 2.0), "W3 Fläche auf Wasserhöhe (y=2.0)")
var mat := mi.material_override
_check(mat is ShaderMaterial and (mat as ShaderMaterial).shader != null,
"W3 Material = Wasser-Shader (dasselbe Verfahren wie der Client)")
if mat is ShaderMaterial:
var sp: Variant = (mat as ShaderMaterial).get_shader_parameter("ufer_tex")
_check(sp != null, "W3 Ufer-Tiefen-Textur gesetzt")
# Matsch-Ring: dieselbe Formel wie der Client (Spiegel-Modul) mischt in
# die Terrain-Zellfarben ein — direkt gegen die Spiegel-Mathe geprüft.
# Knapp unter der Wasserlinie = fast voller Matsch (ufer_faktor ≈ 1),
# 0,25 m tiefe Zelle = im weichen Band-Auslauf, Tiefwasser = 0.
var f_ring := SvmapWasser.ufer_faktor(2.0 - 0.05, 2.0)
var f_mitte := SvmapWasser.ufer_faktor(2.0 - 0.25, 2.0)
var f_tief := SvmapWasser.ufer_faktor(-4.0, 2.0)
_check(f_ring > 0.9, "W3 Matsch-Ring an der Wasserlinie (ufer_faktor > 0.9, ist %.2f)" % f_ring)
_check(f_mitte > 0.4 and f_mitte < f_ring, "W3 Matsch-Band fällt weich ab (0,25 m Tiefe = %.2f)" % f_mitte)
_check(f_tief == 0.0, "W3 Tiefwasser ohne Matsch (ufer_faktor = 0)")
var base := Color(0.9, 0.9, 0.9, 0.25) # Alpha = gepackte Material-ID
var gemischt := SvmapWasser.matsch_anwenden(base, 1.0)
_check(gemischt.a == base.a, "W3 Matsch fasst die Material-ID (Alpha) nicht an")
# Feld entfernen → kein Wasser (alte Maps unverändert).
main.doc.erase("water_level")
vb.call("rebuild", main.doc, main.manifest)
await process_frame
await process_frame # queue_free der alten Kinder braucht 2 Frames
_check(_wasser_node(vb) == null, "W3 ohne water_level → kein Wasser-Node (Legacy)")
func _wasser_node(vb) -> Node3D:
return vb.get_node_or_null("Wasser") as Node3D
func _w4_validator_spiegel() -> void:
print("== W4: Validator-Spiegel (V20-Band wie Match-Server) ==")
# Die Konstanten des Spiegel-Moduls decken die Server-Bandbreite ab.
_check(SvmapWasser.LEVEL_MIN == -5.0 and SvmapWasser.LEVEL_MAX == 500.0,
"W4 Plausibilitäts-Band Spiegel = Server (−5..500)")
# Editor-Validator testet Terrain-Höhenbereich ±10 m — die Server-Regel
# hat der Server-Suite identisch gezeigt; hier der Shader-Spiegel-Vertrag:
_check(ResourceLoader.exists("res://core/viewport/wasser.gdshader"),
"W4 Spiegel-Shader im Editor-Pfad vorhanden")
func _finish() -> void:
print("--------------------------------------------------")
print("CHECKS: %d FAILURES: %d" % [checks, failures])
quit(0 if failures == 0 else 1)