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>
128 lines
5.3 KiB
GDScript
128 lines
5.3 KiB
GDScript
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
|