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)