From 0f86472cfae6df9d77be1083cb772e27ccfa2459 Mon Sep 17 00:00:00 2001 From: scopedvr-operator Date: Tue, 20 Oct 2026 09:37:05 +0000 Subject: [PATCH] T1b-Wiederanbindung (Operator): Test-Startpunkt zurueck nach dem U1-Umbau MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sly-Befund: der Knopf 'Test-Start' fehlte; tests/test_playtest_spawn.gd brach mit "Nonexistent function '_test_start_reload'" ab und hing. - Werkzeug-Modus 'teststart' wieder in _set_tool_mode (+ enter/leave/sync) - ShortcutPolicy.esc_action: exit_teststart inkl. Test - Taste T: Editor = Werkzeug umschalten, Lauf-Modus = Punkt setzen + Teleport aufs Fadenkreuz (playtest.teleport_crosshair) - Entf im Werkzeug loescht den Punkt; Klartext-Hinweis des Werkzeugs wieder in der Statuszeile ('auf dem Boden unter dem Objekt') - Leisten-Knopf '🎯 Test-Start' als Umschalter (registry.gd ACTIONS) - neues Panel-Modul src/ui/panels/test_start_panel.gd + Registry-Eintrag - Haken _test_start_apply/_delete/_reload + test_start_point/test_start_panel/ test_start_btn stehen fest auf main (Vertrag der Pruefsuite) - _test_start_reload() wieder nach Neu/Laden/Speichern; playtest.enter() bekommt den aufgeloesten Startpunkt (TestStart.resolve_start) Belegt: test_playtest_spawn 56/56, test_shortcuts 204, gui_smoke 92, ui_layout 71, Import 0 Fehler. --- src/editor/shortcut_policy.gd | 6 +- src/ui/main.gd | 140 +++++++++++++++++++++++++- src/ui/panels/registry.gd | 6 ++ src/ui/panels/test_start_panel.gd | 63 ++++++++++++ src/ui/panels/test_start_panel.gd.uid | 1 + src/ui/panels/toolbar.gd | 5 + tests/test_shortcuts.gd | 1 + tests/test_ui_layout.gd | 4 +- 8 files changed, 220 insertions(+), 6 deletions(-) create mode 100644 src/ui/panels/test_start_panel.gd create mode 100644 src/ui/panels/test_start_panel.gd.uid diff --git a/src/editor/shortcut_policy.gd b/src/editor/shortcut_policy.gd index 927c831..e6efe0d 100644 --- a/src/editor/shortcut_policy.gd +++ b/src/editor/shortcut_policy.gd @@ -9,7 +9,7 @@ class_name ShortcutPolicy ## 2. Ghost-Preview (Platzieren) abbrechen ## 3. Auswahl aufheben ## Rückgabe-Werte: -## "exit_terrain" | "exit_spawns" | "exit_bauen" | "cancel_grab" +## "exit_terrain" | "exit_spawns" | "exit_bauen" | "exit_teststart" | "cancel_grab" ## "cancel_placement" | "clear_selection" | "none" ## grabbing (Karte t_55257792): G-Greifen läuft — Esc rollt die Positionen ## zurück (Blender-Semantik). Vorrang NACH Werkzeug-Exit (im Terrain-/Bauen- @@ -22,6 +22,10 @@ static func esc_action(tool_mode: String, placing: bool, has_selection: bool, return "exit_spawns" if tool_mode == "bauen": return "exit_bauen" + if tool_mode == "teststart": + # Karte t_96e3c4ee (Wiederanbindung 20.10.): Esc verlässt das + # Test-Start-Werkzeug — der gesetzte Punkt BLEIBT bestehen. + return "exit_teststart" if grabbing: return "cancel_grab" if placing: diff --git a/src/ui/main.gd b/src/ui/main.gd index 1844d0c..fbee0c4 100644 --- a/src/ui/main.gd +++ b/src/ui/main.gd @@ -123,6 +123,15 @@ var _grid_on := false # Nutzer-Wahl für das Maßstabsgitter # Terrain-Werkzeug (F1) — Sly-Befund 17.10.: dauerhaft sichtbar störte. # Effektiv = _contours_fix AN ODER tool_mode == "terrain". var _contours_on := false + +# --- Test-Start (Karte t_96e3c4ee / T1b) ------------------------------------- +# main hält den ZUSTAND und die Haken (Vertrag mit tests/test_playtest_spawn.gd); +# das Panel-Modul test_start_panel.gd zeigt ihn nur an (U1-Muster). +var test_start_point: Variant = null # Vector3 (nur x/z zählt) oder null +var test_start_tool # src/editor/playtest/test_start_tool.gd +var test_start_panel: Control # src/ui/panels/test_start_panel.gd (von der Prüfsuite erwartet) +var test_start_btn: Button # „🎯 Test-Start" in der Leiste (Umschalter) +var _test_start_active := false var _loading_ui_values := false # verhindert dirty beim Auffüllen der Felder # Rahmen-Ausziehen (Rubberband) — Overlay über dem 3D-Viewport @@ -567,6 +576,14 @@ func _build_center_viewport() -> SubViewportContainer: world.add_child(build_tool) build_tool.setup(self, camera, view_builder, interaction) + # Test-Start-Werkzeug (T1b, Wiederanbindung 20.10.): Marker in der Karte, + # Klick setzt den Startpunkt. Lebt — wie die übrigen Werkzeuge — in main, + # gelesen wird es von test_start_tool.gd über main.test_start_point. + test_start_tool = Node3D.new() + test_start_tool.set_script(load("res://src/editor/playtest/test_start_tool.gd")) + world.add_child(test_start_tool) + test_start_tool.setup(self, camera, view_builder, interaction) + # Bedien-Hinweis im Viewport (Overlay) help_label = Label.new() help_label.text = "Linksklick: auswählen & ziehen · Umschalt+Klick: Auswahl erweitern · Rahmen aufziehen: mehrere wählen · Strg+G: gruppieren\nR / Mausrad: drehen · Strg+D: duplizieren · Bild↑/↓: anheben/absenken · Spiegeln rechts · Entf: löschen · Strg+Z: zurück" @@ -1055,14 +1072,17 @@ func _start_playtest() -> void: playtest.exit_requested.connect(_on_playtest_exit_requested) world3d.add_child(playtest) camera.current = false # Editor-Orbit abgeben — die Spiel-Kamera übernimmt - playtest.enter(doc, view_builder) - _set_status("▶ Testlauf läuft: WASD laufen, Shift rennen, Space springen, Maus umsehen — ESC zurück zum Editor.") + # Karte t_96e3c4ee: Fallback-Reihenfolge mit Klartext-Hinweis — gesetzter + # Test-Start → erster Team-Spawnpunkt → Kartenmitte. + var resolved: Dictionary = TestStart.resolve_start(doc, test_start_point) + playtest.enter(doc, view_builder, resolved["pos"]) + _set_status("▶ Testlauf läuft: WASD laufen, Shift rennen, Space springen, Maus umsehen — ESC zurück zum Editor. (%s)" % str(resolved["status"])) ## Editor-Input einfrieren/freigeben (Kamera-Flug, Werkzeuge, Gizmo). ## Nur auf die Werkzeug-Nodes — die Sicht (view_builder) bleibt 1:1 stehen. func _playtest_set_frozen(on: bool) -> void: - for t in [camera, interaction, terrain_tool, spawn_tool, build_tool]: + for t in [camera, interaction, terrain_tool, spawn_tool, build_tool, test_start_tool]: if t == null: continue t.set_process_unhandled_input(not on) @@ -1097,6 +1117,7 @@ func _on_new_map_confirmed() -> void: history.begin(doc) _clear_edit_state() _refresh_all() + _test_start_reload() # Karte t_96e3c4ee: neue Map → kein Test-Start gesetzt _set_status("Neue Map „%s“ erstellt (%.0f × %.0f m, Wetter: %s). Wähle links ein Objekt, um zu bauen!" % [ new_dialog.get_map_name(), new_dialog.get_size_m(), new_dialog.get_size_m(), new_dialog.get_weather()]) @@ -1113,6 +1134,7 @@ func _on_open_selected(path: String) -> void: history.begin(doc) _clear_edit_state() _refresh_all() + _test_start_reload() # Karte t_96e3c4ee: Sidecar der geladenen Map lesen var map_name := str(doc.get("info", {}).get("name", "?")) _set_status("Map „%s“ geladen (%d Objekte). Klicke ein Objekt in der Karte an, um es anzupassen." % [map_name, SvmapDocument.object_count(doc)]) @@ -1150,6 +1172,7 @@ func _save_to(path: String) -> void: return current_path = path dirty = false + _test_start_reload() # Karte t_96e3c4ee: Punkt wandert beim Speichern mit _refresh_file_label() _set_status("Map gespeichert: %s" % path) _refresh_tree() @@ -1306,8 +1329,21 @@ func _unhandled_key_input(event: InputEvent) -> void: if key == KEY_ESCAPE: playtest.handle_escape() get_viewport().set_input_as_handled() + elif key == KEY_T: + # Karte t_96e3c4ee, „jederzeit": T im Testlauf = Test-Start auf den + # Punkt unterm Fadenkreuz setzen + SOFORT dorthin versetzen + # (Augenhöhe 1,70 m, Kollision geprüft). + var tp: Dictionary = playtest.call("teleport_crosshair") + if bool(tp.get("ok")): + _test_start_apply(float(tp.get("x")), float(tp.get("z")), str(tp.get("hinweis"))) + get_viewport().set_input_as_handled() return match key: + KEY_T: + # Karte t_96e3c4ee: T schaltet das Test-Start-Werkzeug um (Editor). + if not ctrl: + _set_tool_mode("teststart" if tool_mode != "teststart" else "objects") + get_viewport().set_input_as_handled() KEY_Z: if ctrl: if event.shift_pressed: @@ -1437,6 +1473,10 @@ func _unhandled_key_input(event: InputEvent) -> void: "exit_bauen": _set_tool_mode("objects") _set_status("Bauen-Modus beendet — zurück zum Objekt-Werkzeug.") + "exit_teststart": + # Der gesetzte Test-Start bleibt bestehen (nur Werkzeug aus). + _set_tool_mode("objects") + _set_status("Test-Start-Werkzeug beendet — zurück zum Objekt-Werkzeug.") "cancel_grab": interaction.grab_cancel() "cancel_placement": @@ -1455,6 +1495,9 @@ func _unhandled_key_input(event: InputEvent) -> void: spawn_tool.call("delete_selected") elif tool_mode == "bauen": build_tool.call("delete_selected") + elif tool_mode == "teststart": + # Karte t_96e3c4ee: Entf im Test-Start-Werkzeug = Punkt löschen. + _test_start_delete() else: interaction.delete_selected() get_viewport().set_input_as_handled() @@ -1615,24 +1658,34 @@ func _set_tool_mode(mode: String) -> void: _sync_terrain_button() _sync_spawn_button() _sync_build_button() + _sync_test_start_button() _update_tool_label() return if mode == "terrain": _leave_spawn_mode() _leave_build_mode() + _leave_test_start_mode() _enter_terrain_mode() elif mode == "spawns": _leave_terrain_mode() _leave_build_mode() + _leave_test_start_mode() _enter_spawn_mode() elif mode == "bauen": _leave_terrain_mode() _leave_spawn_mode() + _leave_test_start_mode() _enter_build_mode() + elif mode == "teststart": + _leave_terrain_mode() + _leave_spawn_mode() + _leave_build_mode() + _enter_test_start_mode() else: _leave_terrain_mode() _leave_spawn_mode() _leave_build_mode() + _leave_test_start_mode() _enter_objects_mode() _update_tool_label() # Karte t_89c0ec95: Werkzeugwechsel = Höhenlinien-Umschalter — nach dem @@ -2477,4 +2530,83 @@ func _start_bauen_demo(args: PackedStringArray) -> void: print("BAUEN_DEMO_OK: 3 Screenshots in %s (%d Bauteile, Map OK)" % [ out_dir, BuildOps.piece_count(doc)]) if args.has("--bauen-demo-quit"): - get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind \ No newline at end of file + get_tree().quit() # Batch-Smoke (xvfb): automatisch beenden, sobald die Shots da sind + + +## --- Test-Start: Haken (T1b-Vertrag, Operator-Wiederanbindung 20.10.) -------- +## Diese drei Namen erwartet tests/test_playtest_spawn.gd (56 checks) auf main. +## Sie waren beim U1-Umbau verschwunden (Suite brach mit „Nonexistent function +## '_test_start_reload'" ab und hing) — deshalb stehen sie hier bewusst fest. +## Der sichtbare Teil (Info + Löschen) lebt in src/ui/panels/test_start_panel.gd, +## der Zustand hier — genau wie vor dem Umbau. +func _test_start_apply(x: float, z: float, hinweis: String) -> void: + test_start_point = Vector3(x, 0.0, z) + TestStartStore.save_point(current_path, test_start_point) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + # Klartext-Hinweis des Werkzeugs (z.B. „auf dem Boden unter dem Objekt") + # mitnehmen — die Prüfsuite liest ihn aus der Statuszeile. + var ort := " (Höhe automatisch)" if hinweis.is_empty() else " (Höhe automatisch — %s)" % hinweis + _set_status("Test-Start: x=%.0f z=%.0f%s — F5 startet den Testlauf genau dort." % [x, z, ort]) + + +func _test_start_delete() -> void: + if test_start_point == null: + return + test_start_point = null + TestStartStore.clear_point(current_path) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + + +func _test_start_reload() -> void: + test_start_point = TestStartStore.load_point(current_path) + if test_start_tool != null: + test_start_tool.call("refresh_from_doc") + _refresh_test_start_panel() + + +func _refresh_test_start_panel() -> void: + if test_start_panel != null and is_instance_valid(test_start_panel): + test_start_panel.call("refresh_point", test_start_point) + + +## --- Test-Start: Werkzeug-Modus (wie Terrain/Spawns/Bauen) -------------------- +## Die Prüfsuite erwartet tool_mode == "teststart", einen leuchtenden Leisten- +## Knopf (`test_start_btn`) und das erst dann sichtbare Panel (`test_start_panel`). +func _enter_test_start_mode() -> void: + interaction.call("cancel_placement") + tool_mode = "teststart" + if test_start_tool != null: + test_start_tool.call("activate") + _sync_test_start_button() + _sync_test_start_panel_visibility() + + +func _leave_test_start_mode() -> void: + if test_start_tool != null: + test_start_tool.call("deactivate") + # Direkt aus-/einschalten statt über tool_mode: _set_tool_mode ruft die + # _leave_*-Funktionen BEVOR der Zielmodus gesetzt ist — ein Abgleich gegen + # tool_mode ließe das Panel offen (Befund aus test_playtest_spawn). + if test_start_panel != null: + test_start_panel.visible = false + if test_start_btn != null: + test_start_btn.button_pressed = false + + +func _sync_test_start_button() -> void: + if test_start_btn != null: + test_start_btn.button_pressed = (tool_mode == "teststart") + + +func _sync_test_start_panel_visibility() -> void: + if test_start_panel != null: + test_start_panel.visible = (tool_mode == "teststart") + + +## Leisten-Knopf „🎯 Test-Start" (registry.gd ACTIONS) — schaltet das Werkzeug um. +func _on_test_start_tool_pressed() -> void: + _set_tool_mode("objects" if tool_mode == "teststart" else "teststart") \ No newline at end of file diff --git a/src/ui/panels/registry.gd b/src/ui/panels/registry.gd index c2511d8..81d369b 100644 --- a/src/ui/panels/registry.gd +++ b/src/ui/panels/registry.gd @@ -36,6 +36,8 @@ const PANELS: Array = [ "skript": "res://src/ui/panels/terrain_panel.gd"}, {"id": "check", "titel": "Prüfen", "gruppe": "rechts", "order": 50, "skript": "res://src/ui/panels/check_panel.gd"}, + {"id": "teststart", "titel": "Test-Start", "gruppe": "rechts", "order": 60, + "skript": "res://src/ui/panels/test_start_panel.gd"}, # --- links: Vorbereitet für U2 (Szenen-Baum); Bibliothek ist UNTEN --- {"id": "szene", "titel": "Map-Inhalt", "gruppe": "links", "order": 10, "skript": "res://src/ui/panels/scene_tree_panel.gd"}, @@ -56,6 +58,10 @@ const ACTIONS: Array = [ "order": 10, "action": "_on_check_map"}, {"id": "karte_testen", "titel": "▶ Karte testen", "gruppe": "toolbar", "order": 20, "action": "_on_test_run"}, + # Wiederanbindung 20.10. (T1b, Karte t_96e3c4ee): der Test-Start-Knopf war + # beim U1-Umbau aus der Leiste verschwunden. + {"id": "teststart", "titel": "🎯 Test-Start", "gruppe": "toolbar", + "order": 30, "action": "_on_test_start_tool_pressed"}, ] diff --git a/src/ui/panels/test_start_panel.gd b/src/ui/panels/test_start_panel.gd new file mode 100644 index 0000000..00fe3ff --- /dev/null +++ b/src/ui/panels/test_start_panel.gd @@ -0,0 +1,63 @@ +extends "res://src/ui/panels/editor_panel.gd" +## Test-Start-Bereich (rechte Spalte — Karte t_96e3c4ee / T1b, Wiederanbindung +## durch den Operator am 20.10.). +## +## Aufteilung: der ZUSTAND lebt in main.gd (`test_start_point`) samt den Haken +## `_test_start_apply` / `_test_start_delete` / `_test_start_reload`, weil die +## Prüfsuite tests/test_playtest_spawn.gd (56 checks) genau diese drei Namen auf +## main erwartet — das ist der T1b-Vertrag. Beim U1-Umbau waren sie verschwunden, +## die Suite brach mit „Nonexistent function '_test_start_reload'" ab. +## Dieses Modul zeigt den Zustand nur an (Info-Text + Löschen-Knopf) und meldet +## sich datengetrieben über registry.gd an — main.gd verdrahtet nichts fest. + +var info: Label +var delete_btn: Button +var info_hint: Label + + +static func spec() -> Dictionary: + return {"titel": "Test-Start", "gruppe": "rechts", "order": 60} + + +func panel_title() -> String: + return "Test-Start (F5)" + + +func build() -> void: + add_child(_header_label("Startpunkt für „▶ Karte testen“")) + info = Label.new() + info.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + add_child(info) + + delete_btn = Button.new() + delete_btn.text = "🗑 Test-Start löschen" + delete_btn.custom_minimum_size = UIScale.v2(0, 40) + delete_btn.tooltip_text = "Test-Startpunkt entfernen — F5 startet dann wieder am ersten Team-Spawnpunkt." + delete_btn.pressed.connect(func() -> void: main.call("_test_start_delete")) + add_child(delete_btn) + + add_child(_separator()) + info_hint = Label.new() + info_hint.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + info_hint.text = "In der Leiste „🎯 Test-Start“ drücken (oder Taste T), dann in die Karte klicken. Die Höhe kommt automatisch vom Terrain. Der Punkt liegt in einer Nebendatei zur .svmap — die Kartendatei selbst bleibt unverändert." + add_child(info_hint) + + # main holt sich das Modul hier ab (main._refresh_test_start_panel) — die + # Prüfsuite liest es als main.test_start_panel. Sichtbar erst, wenn das + # Werkzeug aktiv ist (wie vor dem U1-Umbau). + main.set("test_start_panel", self) + visible = false + refresh_point(main.get("test_start_point")) + + +## Wird von main._refresh_test_start_panel() gerufen. point = Vector3 oder null. +func refresh_point(point: Variant) -> void: + if info == null or delete_btn == null: + return + if point == null: + info.text = "Kein Test-Start gesetzt.\nF5 startet dann am ersten Team-Blau-Spawnpunkt (ohne Spawnpunkte: Kartenmitte)." + delete_btn.disabled = true + else: + var p: Vector3 = point + info.text = "Test-Start: x=%.0f z=%.0f\n(Höhe kommt automatisch vom Terrain)" % [p.x, p.z] + delete_btn.disabled = false diff --git a/src/ui/panels/test_start_panel.gd.uid b/src/ui/panels/test_start_panel.gd.uid new file mode 100644 index 0000000..05aaed2 --- /dev/null +++ b/src/ui/panels/test_start_panel.gd.uid @@ -0,0 +1 @@ +uid://brik7mu7wv0el diff --git a/src/ui/panels/toolbar.gd b/src/ui/panels/toolbar.gd index 7ad8bbc..026ffa9 100644 --- a/src/ui/panels/toolbar.gd +++ b/src/ui/panels/toolbar.gd @@ -131,6 +131,11 @@ func build() -> void: btn.custom_minimum_size = UIScale.v2(190, 40) btn.tooltip_text = "Startet die Map SOFORT als Spiel zum Durchlaufen — eigenes Fenster, der Editor bleibt offen. ESC im Spiel = zurück zum Editor." btn.pressed.connect(main.call.bind(str(entry["action"]))) + # Test-Start ist ein Umschalter: main liest/setzt seinen Zustand + # (Prüfsuite test_playtest_spawn.gd erwartet main.test_start_btn). + if str(entry["id"]) == "teststart": + btn.toggle_mode = true + main.set("test_start_btn", btn) add_child(btn) # UI-Größe (Karte t_297221b5): Klein/Mittel/Groß — Sly stellt das selbst um, diff --git a/tests/test_shortcuts.gd b/tests/test_shortcuts.gd index 8b0b0b6..e106bdd 100644 --- a/tests/test_shortcuts.gd +++ b/tests/test_shortcuts.gd @@ -386,6 +386,7 @@ func _test_esc_policy() -> void: _check(ShortcutPolicy.esc_action("terrain", false, false) == "exit_terrain", "Esc im Terrain-Werkzeug → Werkzeug verlassen") _check(ShortcutPolicy.esc_action("spawns", false, false) == "exit_spawns", "Esc im Spawn-Werkzeug → Werkzeug verlassen") _check(ShortcutPolicy.esc_action("bauen", false, false) == "exit_bauen", "Esc im Bauen-Modus → Werkzeug verlassen") + _check(ShortcutPolicy.esc_action("teststart", false, false) == "exit_teststart", "Esc im Test-Start-Werkzeug → Werkzeug verlassen (Punkt bleibt)") _check(ShortcutPolicy.esc_action("bauen", true, true) == "exit_bauen", "Werkzeug hat VORRANG vor Platzieren") _check(ShortcutPolicy.esc_action("objects", true, true) == "cancel_placement", "Esc beim Platzieren → Ghost-Preview abbrechen") _check(ShortcutPolicy.esc_action("objects", false, true) == "clear_selection", "Esc mit Auswahl → Auswahl aufheben") diff --git a/tests/test_ui_layout.gd b/tests/test_ui_layout.gd index 471cfab..61eb569 100644 --- a/tests/test_ui_layout.gd +++ b/tests/test_ui_layout.gd @@ -101,7 +101,9 @@ func _init() -> void: _check(left != null and left is TabContainer, "linke Spalte ist ein TabContainer (U2-vorbereitet)") _check(bottom != null and bottom is VSplitContainer, "unten: VSplit (größenverstellbar)") if right != null: - _check(right.get_child_count() == 5, "rechts: 5 Tabs (Inspektor/Auswahl/Map/Terrain/Prüfen) — hat %d" % right.get_child_count()) + # 20.10.: „Test-Start" ist als 6. Reiter dazugekommen (Wiederanbindung T1b, + # nachdem U1 die Verdrahtung verloren hatte). + _check(right.get_child_count() == 6, "rechts: 6 Tabs (Inspektor/Auswahl/Map/Terrain/Prüfen/Test-Start) — hat %d" % right.get_child_count()) _check(right.get_tab_count() == right.get_child_count(), "rechts: alle Bereiche als Tab in der Leiste erreichbar (Titel je Bereich)") for i in right.get_tab_count():