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

123 lines
5.6 KiB
GDScript

extends SceneTree
## M5-Beweis (t_60014669): Ein-Klick „Karte symmetrisch machen“ an einer echten
## Testkarte — VORHER/NACHHER aus der Vogelperspektive mit Symmetrie-Achse.
## Karte asymmetrisch: Hügel + Sandfleck nur links, Objekte nur links (plus
## ein widersprüchliches rechts), Bau-Teil nur links, Solo-Spawn links.
## Nach EINEM Klick (SymmetryOps.apply, Quelle links): beide Seiten decken.
##
## xvfb-run -a /opt/data/home/bin/godot4 --rendering-driver opengl3 --path . \
## --resolution 1600x900 -s res://tests/probe_m5_symmetry_render.gd
##
## Ausgabe: /tmp/m5_vorher.png, /tmp/m5_nachher.png + Prüfzeilen.
const SpawnToolScript := preload("res://core/editor/spawn_tool.gd")
const POINT := SymmetryCore.Mode.POINT
func _initialize() -> void:
var manifest: Dictionary = JSON.parse_string(FileAccess.get_file_as_string("res://library/objects.json"))
var doc: Dictionary = SvmapDocument.create_new("M5-Symmetrie-Beweis", 256.0, "mittag")
_fill_asymmetric(doc)
# --- VORHER rendern --------------------------------------------------------
var bauer: Node3D = load("res://core/viewport/map_view_builder.gd").new()
root.add_child(bauer)
await process_frame
bauer.rebuild(doc, manifest)
await process_frame
var cam := Camera3D.new()
cam.fov = 50.0
root.add_child(cam)
cam.make_current()
var achse := MeshInstance3D.new()
var achse_mesh := BoxMesh.new()
achse_mesh.size = Vector3(0.4, 3.0, 120.0)
achse.mesh = achse_mesh
var achse_mat := StandardMaterial3D.new()
achse_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
achse_mat.albedo_color = Color(1.0, 0.95, 0.2, 0.55)
achse_mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
achse.material_override = achse_mat
achse.position = Vector3(0.0, 1.5, 0.0)
root.add_child(achse)
await _shot(cam, "/tmp/m5_vorher.png", Vector3(0.0, 95.0, 105.0))
# --- Prüfzeilen VORHER (echte Werte) ---------------------------------------
var rep_v := SymmetryOps.check(doc, POINT)
print("VORHER-CHECK: %s" % str(rep_v["text"]))
print("VORHER-BODEN: Hügel Süd-West %.2f m | Spiegelpunkt Nord-Ost %.2f m" % [
SvmapDocument.terrain_height_at(doc, -90.0, 50.0),
SvmapDocument.terrain_height_at(doc, 90.0, -50.0)])
print("VORHER-OBJEKTE: %d | BAU-TEILE: %d" % [
(doc["objects"] as Array).size(), BuildOps.piece_count(doc)])
# --- EIN Klick: ganze Karte angleichen (Quelle links) ----------------------
var res := SymmetryOps.apply(doc, POINT, false)
print("APPLY-BERICHT: %s" % str(res["report"]))
bauer.rebuild(doc, manifest)
await _shot(cam, "/tmp/m5_nachher.png", Vector3(0.0, 95.0, 105.0))
# --- Prüfzeilen NACHHER ----------------------------------------------------
var rep_n := SymmetryOps.check(doc, POINT)
print("NACHHER-CHECK: %s" % str(rep_n["text"]))
print("NACHHER-BODEN: Süd-West %.2f m | Nord-Ost %.2f m" % [
SvmapDocument.terrain_height_at(doc, -90.0, 50.0),
SvmapDocument.terrain_height_at(doc, 90.0, -50.0)])
print("NACHHER-OBJEKTE: %d | BAU-TEILE: %d | Spawn-Partner A9=%s" % [
(doc["objects"] as Array).size(), BuildOps.piece_count(doc),
SpawnToolScript.spawn_mirror_partner(doc, "A9")])
var fehler: Array = []
_check(bool(rep_n["ok"]), "Check nach dem Klick: 0 Abweichungen", fehler)
_check(absf(SvmapDocument.terrain_height_at(doc, -90.0, 50.0)
- SvmapDocument.terrain_height_at(doc, 90.0, -50.0)) < 0.001,
"Boden: Hügelhöhe am Spiegelpunkt identisch", fehler)
for f in fehler:
print(" FEHLER: %s" % f)
print("PROBE_M5_%s" % ("FAIL" if not fehler.is_empty() else "OK"))
quit(1 if not fehler.is_empty() else 0)
func _fill_asymmetric(doc: Dictionary) -> void:
# Layout-Kohärenz: Hügelregion Süd-West (i<60, j 82..96) und ihr
# Punkt-Spiegel Nord-Ost. Objekte/Bauteil sitzen AUF dem Hügel (y=6) —
# nach dem Angleichen sitzen ihre Zwillinge exakt (M3-Konvention: y 1:1).
# Spawns liegen außerhalb der Hügelzone (y 0.2 bleibt sichtbar).
var terrain: Dictionary = doc["terrain"]
var res := int(terrain["resolution"])
var heights := SvmapIO.decode_heights(terrain)
for j in range(82, 97):
for i in 60:
heights[j * res + i] = 6.0 # Hügel nur Süd-West
terrain["heightmap_r16_b64"] = TerrainField.encode(heights,
float(terrain["min_height_m"]), float(terrain["max_height_m"]))
var mats := SvmapIO.decode_materials(terrain)
for j in range(84, 93):
for i in range(20, 30):
mats[j * res + i] = 1 # Sandfleck nur Süd-West
terrain["materials_r8_b64"] = SvmapIO.encode_materials(mats)
var objs: Array = doc["objects"]
objs.append({"id": "barrel_001", "pos": [-90.0, 6.0, 50.0], "scale": 1.0, "rot_y_deg": 30.0}) # Hügel
objs.append({"id": "barrel_001", "pos": [90.0, 6.0, -50.0], "scale": 1.0, "rot_y_deg": 10.0}) # Widerspruch (Spiegelpunkt)
objs.append({"id": "barrel_001", "pos": [110.0, 6.0, -60.0], "scale": 1.0, "rot_y_deg": 0.0}) # solo rechts
var fund := BuildOps.make_piece("fundament", Vector3(-90.0, 6.0, 40.0), 0.0)
BuildOps.ensure_block(doc).append(fund) # nur links (auf dem Hügel), kein Zwilling
SpawnOps.remove_point(doc, 1, "B1") # roter Punkt weg → A4 solo
var zones: Dictionary = doc["spawn_zones"]
((zones["0"] as Dictionary)["points"] as Array).append({"id": "A9", "pos": [-60.0, 6.0, 40.0]})
func _shot(cam: Camera3D, path: String, pos: Vector3) -> void:
cam.position = pos
await process_frame
cam.look_at(Vector3(0.0, 0.0, -5.0), Vector3(0.0, 0.0, -1.0))
await process_frame
await process_frame
await RenderingServer.frame_post_draw
root.get_texture().get_image().save_png(path)
print("BILD: %s" % path)
func _check(cond: bool, label: String, fehler: Array) -> void:
print(" %s: %s" % ["ok" if cond else "FEHLER", label])
if not cond:
fehler.append(label)