202 lines
8.6 KiB
GDScript
202 lines
8.6 KiB
GDScript
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)
|