scopedvr-map-editor/tests/test_mal_fein.gd
slyphax 69668eb250 Feines Material-Raster: Malen auf ~1 m statt Höhenraster (bei 1024 m vorher 8 m)
Sly: „feiner malen“. Die Material-Schicht bekommt ein eigenes Raster
(terrain.material_resolution, optional; Default = resolution → alte Karten
unverändert gültig): ~1 m pro Punkt, auf dem Höhenraster ausgerichtet,
max. 1025 (1024 m → 1025, 256 m → 257, 2048 m → 1025 = 2 m).

- SvmapIO: material_resolution / material_resolution_for / resample /
  upgrade_material_layer / material_at_height_point; V15 prüft Raster + Länge.
- Neue Karten starten fein; beim Öffnen werden alte Karten optisch identisch
  angehoben (Datei ändert sich erst beim Speichern); Resize rechnet die
  Schicht auf ihrem eigenen Raster an der Weltposition um.
- Pinsel, Spiegeln (Symmetrie live + „Symmetrie anwenden“) und Live-Splat
  arbeiten auf dem Material-Raster; Vertex-Farben lesen den passenden Punkt.
- Malen-Hinweis zeigt die echte feinste Einheit.
- Tests: test_mal_fein 22/22 (Raster, Anheben, 2-m-Pinsel ≈ 13 Punkte,
  V15, Roundtrip, Resize); test_terrain/test_terrain_mirror/
  test_symmetry_onclick auf das neue Raster nachgezogen; Identitäts-Runner OK.
  Kette Editor → Server (Prüfsumme) → Client per run_map_download_e2e 15/15.

Format-Spiegel: Match-Server svmap_loader (V15) + Client svmap_terrain.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 00:07:25 +02:00

128 lines
5.3 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-Tests (headless): feines Material-Raster (Karte 24.10., Sly: „feiner malen“).
## godot --headless --path . -s res://tests/test_mal_fein.gd
##
## F1 Ziel-Raster: 1024 m/129 → 1025 (1 m), 256 m/129 → 257 (1 m), 2048 m → 1025.
## F2 Neue Map startet mit feinem Raster und ist sofort gültig (V1–V16).
## F3 Alte Karte (Raster = Höhenraster) wird beim Anheben optisch identisch
## übernommen und ist danach gültig.
## F4 Pinsel 2 m auf 1024-m-Map ändert nur ~π·2² Punkte (nicht eine 8-m-Zelle).
## F5 V15 lehnt kaputte material_resolution / falsche Länge ab.
## F6 Speichern → Laden → identische Schicht (Roundtrip mit Prüfsumme).
## F7 Resize (1024 → 2048 m) hält die gemalte Stelle an derselben Weltposition.
var failures := 0
var checks := 0
func _initialize() -> void:
_f1()
_f2_f4_f6_f7()
_f3()
_f5()
print("test_mal_fein: %d/%d ok" % [checks - failures, checks])
quit(1 if failures > 0 else 0)
func _check(cond: bool, label: String) -> void:
checks += 1
print("%s: %s" % ["ok " if cond else "FAIL", label])
if not cond:
failures += 1
func _f1() -> void:
_check(SvmapIO.material_resolution_for(129, 1024.0, 1024.0) == 1025, "F1 1024 m/129 → 1025")
_check(SvmapIO.material_resolution_for(129, 256.0, 256.0) == 257, "F1 256 m/129 → 257")
_check(SvmapIO.material_resolution_for(129, 2048.0, 2048.0) == 1025, "F1 2048 m → gedeckelt 1025")
_check(SvmapIO.material_resolution_for(129, 64.0, 64.0) == 129, "F1 kleine Map bleibt beim Höhenraster")
func _f2_f4_f6_f7() -> void:
var doc := SvmapDocument.create_new("Fein", 1024.0, "mittag")
var t: Dictionary = doc["terrain"]
_check(SvmapIO.material_resolution(t) == 1025, "F2 neue Map: material_resolution 1025")
_check(SvmapIO._validate_core(doc).is_empty(), "F2 neue Map gültig (%s)" % str(SvmapIO._validate_core(doc)))
var mats := SvmapIO.decode_materials(t)
_check(mats.size() == 1025 * 1025, "F2 Schicht hat 1025² Punkte")
# F4: Pinsel 2 m Sand mitten auf die Map
var changed := TerrainField.apply_material_brush(mats, 1025, 1024.0, 1024.0, 10.3, -7.6, 2.0, 1)
var n := 0
for v in mats:
if v == 1:
n += 1
_check(changed and n >= 8 and n <= 20, "F4 2-m-Pinsel ändert %d Punkte (≈ π·2² = 12,6; alt: 1 Zelle à 8 m)" % n)
t["materials_r8_b64"] = SvmapIO.encode_materials(mats)
# F6: Roundtrip
var path := "user://test_mal_fein.svmap"
var sv := SvmapIO.save_to_file(path, doc)
_check(bool(sv["ok"]), "F6 speichern ok (%s)" % str(sv.get("errors", [])))
var ld := SvmapIO.load_from_file(path)
_check(bool(ld["ok"]), "F6 laden ok (%s)" % str(ld.get("errors", [])))
if bool(ld["ok"]):
var m2 := SvmapIO.decode_materials(ld["data"]["terrain"])
_check(m2 == mats, "F6 Schicht bitgleich nach Roundtrip")
DirAccess.remove_absolute(ProjectSettings.globalize_path(path))
# F7: Resize auf 2048 m — Sand bleibt bei (10.3, -7.6)
var big := SvmapDocument.resize_terrain(doc, 2048.0, 2048.0)
var bt: Dictionary = big["terrain"]
var bm := SvmapIO.material_resolution(bt)
var bmats := SvmapIO.decode_materials(bt)
var gx := int(roundf((10.3 / 2048.0 + 0.5) * float(bm - 1)))
var gz := int(roundf((-7.6 / 2048.0 + 0.5) * float(bm - 1)))
_check(bmats.size() == bm * bm and int(bmats[gz * bm + gx]) == 1,
"F7 Resize 2048 m (Raster %d): Sand an derselben Weltstelle" % bm)
_check(SvmapIO._validate_core(big).is_empty(),
"F7 Resize-Ergebnis gültig")
func _f3() -> void:
# Alte Karte simulieren: Material-Raster = Höhenraster (129), ohne Feld.
var doc := SvmapDocument.create_new("Alt", 1024.0, "mittag")
var t: Dictionary = doc["terrain"]
var old := PackedByteArray()
old.resize(129 * 129)
for i in old.size():
old[i] = (i * 7) % 20
t.erase("material_resolution")
t["materials_r8_b64"] = SvmapIO.encode_materials(old)
_check(SvmapIO._validate_core(doc).is_empty(), "F3 alte Karte (ohne Feld) gültig")
_check(SvmapIO.upgrade_material_layer(t), "F3 Anheben meldet Änderung")
_check(SvmapIO.material_resolution(t) == 1025, "F3 danach Raster 1025")
var m := SvmapIO.decode_materials(t)
var same := true
for z in 129:
for x in 129:
if SvmapIO.material_at_height_point(m, 1025, 129, x, z) != int(old[z * 129 + x]):
same = false
_check(same, "F3 jeder Höhenpunkt behält sein Material (optisch identisch)")
_check(SvmapIO._validate_core(doc).is_empty(), "F3 angehobene Karte gültig")
_check(not SvmapIO.upgrade_material_layer(t), "F3 zweites Anheben ändert nichts")
func _f5() -> void:
var doc := SvmapDocument.create_new("Kaputt", 1024.0, "mittag")
var t: Dictionary = doc["terrain"]
t["material_resolution"] = 1000 # (1000−1) kein Vielfaches von 128
_check(_has_v15(SvmapIO._validate_core(doc)), "F5 material_resolution nicht auf dem Raster → V15")
t["material_resolution"] = 2049
_check(_has_v15(SvmapIO._validate_core(doc)), "F5 material_resolution > 1025 → V15")
t["material_resolution"] = 1025
var short := PackedByteArray()
short.resize(129 * 129)
t["materials_r8_b64"] = SvmapIO.encode_materials(short)
_check(_has_v15(SvmapIO._validate_core(doc)), "F5 Schichtlänge passt nicht → V15")
func _has_v15(errs: PackedStringArray) -> bool:
for e in errs:
if str(e).begins_with("V15"):
return true
return false
func _only_checksum(errs: PackedStringArray) -> bool:
for e in errs:
if not str(e).begins_with("V13"):
return false
return true