extends SceneTree ## D2-Renderprobe (Karte t_33250710): Map-Builder-Beweis — drei Absperrungen/ ## Hindernisse aus dem Military-Pack werden über den ECHTEN Editor-Viewport-Bauer ## (src/viewport/map_view_builder.gd) auf flachem Boden platziert, perspektivisch ## gerendert und als PNG gespeichert (visuelle Prüfung: stehen die Objekte auf ## dem Boden?). Zusätzlich wird je Objekt gemessen: Unterkante vs. Bodenhöhe, ## Mesh-Größe vs. collision.size. ## ## BRAUCHT Rendering (nicht --headless): ## xvfb-run -a /opt/data/home/bin/godot4 --path . --rendering-driver opengl3 \ ## -s res://tests/probe_d2_barriers_render.gd ## Ausgabe: PROBE_D2_RENDER_OK / PROBE_D2_RENDER_FAIL const OUT_DIR := "/opt/data/kanban/workspaces/t_33250710/artifacts" const OBJEKTE := [ {"id": "barrier_001", "pos": [-7.0, 0.0, 0.0], "rot_y_deg": 0.0, "scale": 1.0}, {"id": "obstacle_004", "pos": [0.0, 0.0, -1.0], "rot_y_deg": 90.0, "scale": 1.0}, {"id": "obstacle_008", "pos": [8.0, 0.0, 1.0], "rot_y_deg": 45.0, "scale": 1.0}, ] const W := 1280 const H := 720 var fehler: Array = [] func _initialize() -> void: _run() func _run() -> void: await process_frame await process_frame DirAccess.make_dir_recursive_absolute(OUT_DIR) var manifest: Dictionary = JSON.parse_string( FileAccess.get_file_as_string(SvmapIO.MANIFEST_PATH)) if manifest.is_empty(): fehler.append("Bibliothek (library/objects.json) nicht ladbar") var lib: Dictionary = manifest.get("objects", {}) print("Bibliothek: %d Objekte" % lib.size()) var vp := SubViewport.new() vp.size = Vector2i(W, H) vp.own_world_3d = true vp.render_target_update_mode = SubViewport.UPDATE_ALWAYS root.add_child(vp) var builder := Node3D.new() builder.set_script(load("res://src/viewport/map_view_builder.gd")) vp.add_child(builder) builder.call("rebuild", _doku(), manifest) var cam := Camera3D.new() vp.add_child(cam) cam.position = Vector3(0.0, 7.0, 17.0) cam.look_at(Vector3(0.0, 0.8, 0.0)) cam.current = true print("fehlende GLBs (Platzhalter): %s" % str(builder.get("fehlende_glbs"))) for o in OBJEKTE: if not lib.has(o["id"]): fehler.append("%s fehlt in der Bibliothek" % o["id"]) continue _pruefe(builder, lib[o["id"]], o) if not builder.get("fehlende_glbs").is_empty(): fehler.append("Platzhalter statt GLB: %s" % str(builder.get("fehlende_glbs"))) await process_frame await process_frame await RenderingServer.frame_post_draw await process_frame var img := vp.get_texture().get_image() var pfad := OUT_DIR + "/d2_barriers_map_builder.png" img.save_png(pfad) print("PROBE_SAVED %s (%dx%d)" % [pfad, img.get_width(), img.get_height()]) _check_bild(img) for f in fehler: print(" FEHLER: %s" % f) print("PROBE_D2_RENDER_%s" % ("FAIL" if not fehler.is_empty() else "OK")) quit(0 if fehler.is_empty() else 1) func _pruefe(builder: Node, eintrag: Dictionary, obj: Dictionary) -> void: var oid := str(obj["id"]) var markers: Array = builder.get("object_markers") var marker: Node3D = null var idx := -1 for i in OBJEKTE.size(): if str(OBJEKTE[i]["id"]) == oid and i < markers.size(): marker = markers[i] idx = i if marker == null: fehler.append("%s: kein Marker im Viewport-Bauer" % oid) return var box := _mesh_aabb(marker, marker) var col: Dictionary = eintrag.get("collision", {}) var csize: Array = col.get("size", []) var dmax := 0.0 for k in 3: dmax = maxf(dmax, absf(box.size[k] - float(csize[k]))) var echt: bool = bool(marker.get_meta("glb", false)) print("--- %s (Marker %d): echtes GLB=%s" % [oid, idx, str(echt)]) print(" Mesh-AABB lokal: pos=%s size=%s" % [str(box.position), str(box.size)]) print(" Unterkante y=%.3f (Boden %.3f, Marker y=%.3f) | size-Abweichung %.3f m" % [box.position.y, 0.0, marker.position.y, dmax]) if not echt: fehler.append("%s: Platzhalter statt GLB im Editor" % oid) if absf(box.position.y) > 0.02: fehler.append("%s: Unterkante liegt %.3f m neben dem Boden" % [oid, box.position.y]) if dmax > 0.02: fehler.append("%s: Mesh-Größe weicht %.3f m von collision.size ab" % [oid, dmax]) ## Grobe Sichtprüfung im Bild: es muss Struktur (helle Pixel) geben und das ## Objektband in der Bildmitte muss sich vom Himmel/Untergrund unterscheiden. func _check_bild(img: Image) -> void: var w := img.get_width() var h := img.get_height() var hell := 0 var band := 0 for y in range(0, h, 2): for x in range(0, w, 2): var c := img.get_pixel(x, y) var lum := (c.r + c.g + c.b) / 3.0 if lum > 0.55: hell += 1 if y > int(h * 0.35) and y < int(h * 0.85): band += 1 print("Bild: helle Pixel %d, davon im Objektband %d" % [hell, band]) if hell < 200: fehler.append("Renderbild zeigt fast nichts (helle Pixel %d) — Objekte unsichtbar?" % hell) if band < 50: fehler.append("kein Objekt im erwarteten Bildband (helle Pixel %d)" % band) func _mesh_aabb(n: Node, bezug: Node = null) -> AABB: var box := AABB() var erst := true var basis: Transform3D = Transform3D.IDENTITY if bezug == null else bezug.global_transform.affine_inverse() for k in n.get_children(): if k is MeshInstance3D and (k as MeshInstance3D).mesh != null: var aabb: AABB = (k as MeshInstance3D).mesh.get_aabb() aabb = (basis * (k as MeshInstance3D).global_transform) * aabb box = aabb if erst else box.merge(aabb) erst = false var kind := _mesh_aabb(k, bezug) if kind.size != Vector3.ZERO: box = kind if erst else box.merge(kind) erst = false return box ## Flaches Bodenfeld (17², 0 m) — Objekte stehen direkt auf y=0. func _doku() -> Dictionary: var res := 17 var raw := PackedByteArray() raw.resize(res * res * 2) return { "format_version": 2, "info": {"name": "D2 Absperrungen", "author": "Tests", "version": 1}, "terrain": { "type": "heightmap", "resolution": res, "size_x_m": 64.0, "size_z_m": 64.0, "min_height_m": 0.0, "max_height_m": 4.0, "heightmap_r16_b64": Marshalls.raw_to_base64(raw), }, "weather": "mittag", "objects": OBJEKTE, "spawns": [], }