extends SceneTree ## FPS-Probe (Karte t_1da96275, Punkt 5 der Spec): Playtest mit ECHTER Physik ## (SubViewport + World3D + CharacterBody3D + Trimesh-Kollider: Terrain + ## several Gebäude/Bauteile) darf die Editor-Sicht NICHT unter ~60 fps drücken. ## ## Ausführen (braucht einen Bildschirm — virtueller Screen): ## xvfb-run -a godot4 --path . --rendering-driver opengl3 -s res://tests/probe_playtest_fps.gd ## Messung RELATIV (der Container rendert Software-OpenGL/llvmpipe — die ## absolute FPS-Zahl ist dort ein Render-Artefakt): Editor-Sicht OHNE Playtest ## vs. MIT Playtest-Physik (SubViewport + World3D + CharacterBody3D + ## Trimesh-Kollider). Exit 0, wenn der Physik-Zusatz ≤ 20 % Kosten bringt und ## die Editor-Basis unverändert flüssig bleibt (echte 60-fps-Abnahme: Sly-Test). var main: Control func _initialize() -> void: _run.call_deferred() func _run() -> void: var scene: PackedScene = load("res://core/scenes/main.tscn") main = scene.instantiate() root.add_child(main) await process_frame await process_frame # Probe-Map: Terrain + mehrere Objekte (Trimeshe) + Bauteile — echte Last. var doc: Dictionary = SvmapDocument.create_new("FPS-Probe", 200.0, "mittag") doc["objects"] = [ {"id": "building_001", "pos": [0.0, 0.0, -30.0], "rot_y_deg": 0.0, "scale": 1.0}, {"id": "barracks_001", "pos": [20.0, 0.0, 30.0], "rot_y_deg": 90.0, "scale": 1.0}, {"id": "hangar_001", "pos": [-40.0, 0.0, 20.0], "rot_y_deg": 45.0, "scale": 1.0}, {"id": "container_001", "pos": [10.0, 0.0, 10.0], "rot_y_deg": 0.0, "scale": 1.0}, {"id": "tent_001", "pos": [-20.0, 0.0, -10.0], "rot_y_deg": 0.0, "scale": 1.0}, {"id": "box_006", "pos": [6.0, 0.0, 4.0], "rot_y_deg": 0.0, "scale": 1.0}, ] var build_ops := load("res://core/editor/build_ops.gd") var teile: Array = [] for i in 24: var kind: String = ["wand", "boden", "tuer", "treppe"][i % 4] var px := -16.0 + float(i % 6) * 2.0 var pz := 40.0 + float(i / 6) * 2.0 teile.append(build_ops.call("make_piece", kind, Vector3(px, 0.0, pz), 0.0)) doc["gebaeude"] = teile doc["spawn_zones"] = {"0": {"points": [{"id": "A1", "pos": [0.0, 0.0, 20.0]}, {"id": "A2", "pos": [5.0, 0.0, 20.0]}, {"id": "A3", "pos": [-5.0, 0.0, 20.0]}, {"id": "A4", "pos": [10.0, 0.0, 20.0]}]}} main.set("doc", doc) main.set("current_path", "") main.set("dirty", false) main.get("history").begin(doc) main.call("_clear_edit_state") main.call("_refresh_all") await process_frame await process_frame # --- Basis: Editor-Sicht OHNE Playtest (240 Frames) ---------------------- await process_frame var basis: Dictionary = await _messfenster(240) print("FPS_PROBE ZAHLEN Basis (ohne Playtest): avg=%.1f min=%.0f max=%.0f" % [basis.avg, basis.min, basis.max]) main.call("_on_test_run") await process_frame var pt: Node = main.get("playtest") if pt == null: print("FPS_PROBE FAIL: Playtest läuft nicht") quit(1) return var body: Node = pt.get("body") # Auto-Lauf durch die Szene (Bewegung + Physik + Render gleichzeitig). body.set("auto_move_dir", Vector3(0, 0, -1)) var kollider: Node3D = pt.get("_kollider_root") print("FPS_PROBE: Kollider-Kinder=%d (Terrain + Objekte + Bauteile)" % kollider.get_child_count()) # --- Playtest: Physik + Render (240 Frames) ------------------------------ await process_frame var mit: Dictionary = await _messfenster(240) body.set("auto_move_dir", Vector3.ZERO) print("FPS_PROBE ZAHLEN Playtest (mit Physik): avg=%.1f min=%.0f max=%.0f" % [mit.avg, mit.min, mit.max]) var overhead := 0.0 if basis.avg > 0.0: overhead = (basis.avg - mit.avg) / basis.avg * 100.0 print("FPS_PROBE ZAHLEN Overhead: %.1f %%" % overhead) if overhead <= 20.0: print("FPS_PROBE OK: Physik-Zusatz %.1f %% ≤ 20 %% — keine Editor-Regression" % overhead) quit(0) else: print("FPS_PROBE FAIL: Physik-Zusatz %.1f %% > 20 %%" % overhead) quit(1) func _messfenster(frames: int) -> Dictionary: var n_min := 9999.0 var n_max := 0.0 var sum := 0.0 for i in frames: await process_frame var fps := float(Engine.get_frames_per_second()) n_min = minf(n_min, fps) n_max = maxf(n_max, fps) sum += fps return {"avg": sum / float(frames), "min": n_min, "max": n_max}