From db6bcec9715f652b956ac1f9029094b09167ecfa Mon Sep 17 00:00:00 2001 From: "Hermes (godot-vr)" Date: Sat, 17 Oct 2026 17:45:37 +0000 Subject: [PATCH] Flugmodus-Tasten: Leertaste=hoch, Shift=runter, Strg=schneller (Sly 17.10., Shooter-Konvention) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InputMap (shortcut_keys.gd): editor_fly_up=Space, editor_fly_down=Shift, editor_fly_fast=Ctrl (vorher E/Q, Shift=fast). E/Q sauber entfernt, keine Aliasse (Sly-Freispruch). Alt=langsamer unveraendert, Mausrad bleibt Grundtempo. - Konflikt-Check (verpflichtend): Space/Shift/Ctrl allein triggern KEINE Werkzeug-Aktion — neue Testgruppe prueft InputMap-Matches (kein Undo/Save/ Redo/Pinsel/Freelook-Toggle ohne Combo-Partner) + echtes Terrain-Werkzeug: Tasten-Events starten keinen Strich. Strg+LMB-Senken bleibt LMB-gebunden, Entf/Strg+Z unangetastet. - orbit_camera._enter_fly gibt den GUI-Fokus frei, damit Leertaste keinen fokussierten Button ausloest (Space=ui_accept, Bibliothek=Platzierung). - Livetest: Leertaste hebt/Shift senkt die Kamera im Flug (orbit_camera). - Docs: docs/tasten.md, H-Overlay (help_text.gd), README, Status-/Hilfe- Texte der Kamera aktualisiert. - Test-Hygiene: _test_camera_fly_roundtrip ist jetzt Coroutine (await), view_yaw-Orbit-Toleranz 0.01 Grad (float32-Rundtrip-Drift ~2e-4 Grad/Exit). - Suite: 11 Suiten gruen (test_shortcuts 198 checks). --- README.md | 5 +- docs/tasten.md | 12 ++-- src/editor/fly_math.gd | 9 +-- src/editor/shortcut_keys.gd | 20 +++++-- src/ui/help_text.gd | 6 +- src/ui/main.gd | 2 +- src/viewport/orbit_camera.gd | 19 ++++-- tests/test_shortcuts.gd | 108 ++++++++++++++++++++++++++++++++--- 8 files changed, 148 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index df9acb4..18df7e9 100644 --- a/README.md +++ b/README.md @@ -305,8 +305,9 @@ Shortcut-Karten — Karten 2/3 ergänzen dort. Windows-Layouts identisch, keine OS-Hardcodes. Jede Objekt-Operation = EIN ActionHistory-Eintrag (Ausnahme: reine Kamera-Aktionen). - **Freelook-Flug:** Rechte Maustaste GEDACHT halten = Umsehen + Flug - (W/S/A/D + E/Q, Umschalt = ×3, Alt = ×0,3, Mausrad = Grundgeschwindigkeit - DAUERHAFT). Umschalt+F = Flug dauerhaft togglen. Orbit bleibt Default — + (W/S/A/D + Leertaste hoch/Shift runter, Strg = ×3, Alt = ×0,3, Mausrad = + Grundgeschwindigkeit DAUERHAFT). Umschalt+F = Flug dauerhaft togglen. + Orbit bleibt Default — beim Flug-Exit werden Ziel/Winkel/Abstand aus Flugposition/Blickrichtung zurückgerechnet (kein Kamera-Sprung, getestet). Mathe pur in `src/editor/fly_math.gd`, Kamera dünn (`src/viewport/orbit_camera.gd`). diff --git a/docs/tasten.md b/docs/tasten.md index d23bce1..64d7128 100644 --- a/docs/tasten.md +++ b/docs/tasten.md @@ -27,9 +27,9 @@ Konventionen (alle Karten): | Mittlere Maustaste ziehen | Orbit: Um die Karte drehen | | Mittlere Maustaste + Umschalt ziehen | Pan: Karte verschieben | | Mausrad | Zoom (aus, wenn Mausrad gerade ein Objekt dreht) | -| **Rechte Maustaste GEDACHT halten** | **Freelook-Flug:** Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter. **Funktioniert in JEDEM Werkzeug** (Terrain, Bauen, Spawn-Zonen, Platzieren) — die rechte Maustaste hat ABSOLUTEN Vorrang, kein Werkzeug belegt sie (Karte t_2470de89) | -| Umschalt (im Flug) | schneller fliegen (×3) | -| Alt (im Flug) | langsamer fliegen (×0,3) | +| **Rechte Maustaste GEDACHT halten** | **Freelook-Flug:** Umsehen + W vor / S zurück / A links / D rechts; **Leertaste hoch / Umschalt runter** (Shooter-Konvention wie Half-Life/Counter-Strike — Sly-Wunsch 17.10.). **Funktioniert in JEDEM Werkzeug** (Terrain, Bauen, Spawn-Zonen, Platzieren) — die rechte Maustaste hat ABSOLUTEN Vorrang, kein Werkzeug belegt sie (Karte t_2470de89) | +| Strg halten (im Flug) | schneller fliegen (×3) — temporär, Godot-üblich | +| Alt halten (im Flug) | langsamer fliegen (×0,3) | | Mausrad (im Flug) | Grundgeschwindigkeit dauerhaft ändern (2–300 m/s) | | **Umschalt+F** | Freelook-Flug dauerhaft an/aus (Flug bleibt ohne gedrückte rechte Maustaste aktiv) | | **F** | Auswahl ins Bild zentrieren (Focus Selection) | @@ -98,7 +98,11 @@ nichts geht versehentlich verloren. Konfliktfreiheit: Die Ziffern 1–4 sind NUR im Terrain-Werkzeug belegt — im Objekt-/Bauen-Modus sind sie unbenutzt, im Flugmodus (rechte Maustaste -gehalten) bleiben E/Q die Kamera. +gehalten) bleiben Leertaste/Shift/Strg die Kamera. **Leertaste, Umschalt und +Strg triggern im Flug KEINE Werkzeug-Aktion:** Terrain senkt nur bei +Strg+gedrücktem Linksklick (nicht bei Strg allein), Entf/Strg+Z & Co. bleiben +unangetastet, und der Flug-Einstieg gibt den GUI-Fokus frei, damit Leertaste +keinen fokussierten Button auslöst. ## Werkzeuge (Karte 3/3: F-Tasten wechseln das Werkzeug) diff --git a/src/editor/fly_math.gd b/src/editor/fly_math.gd index 2d97178..22276b5 100644 --- a/src/editor/fly_math.gd +++ b/src/editor/fly_math.gd @@ -12,8 +12,8 @@ class_name FlyMath ## im Flug erlaubt: −85…+85° (auch von unten hochschauen). const BASE_SPEED_MPS := 30.0 # Grundgeschwindigkeit (Mausrad ändert sie dauerhaft) -const FAST_MULT := 3.0 # Umschalt = schneller -const SLOW_MULT := 0.3 # Alt = langsamer +const FAST_MULT := 3.0 # Strg halten = schneller (Shooter-Konvention) +const SLOW_MULT := 0.3 # Alt halten = langsamer const WHEEL_MULT := 1.15 # Mausrad-Tick: +15 % / −13 % const SPEED_MIN_MPS := 2.0 const SPEED_MAX_MPS := 300.0 @@ -21,7 +21,7 @@ const SPEED_MAX_MPS := 300.0 const PITCH_MAX_DEG := 85.0 # Flug-Blick grenzwertig (±) — kein Gimbal-Kippen -## Flug-Tempo: Umschalt dominiert, wenn (unüblicherweise) beide Modifier unten sind. +## Flug-Tempo: Strg dominiert, wenn (unüblicherweise) beide Modifier unten sind. static func speed_for(base_mps: float, fast: bool, slow: bool) -> float: if fast: return base_mps * FAST_MULT @@ -39,7 +39,8 @@ static func wheel_speed(base_mps: float, up: bool) -> float: ## Bewegungs-Delta für einen Frame. forward = BLICKRICHTUNG (3D, mit Pitch — ## W/S fliegen dorthin, wo man hinsieht), right = horizontal-rechts, und -## vertikal ist Welt-UP (E/Q heben/senken unabhängig vom Blick). +## vertikal ist Welt-UP (Leertaste/Shift heben/senken unabhängig vom Blick — +## Shooter-Konvention, Sly-Wunsch 17.10.). ## Diagonal-Eingaben werden normalisiert (kein √2-Tempo). Rückgabe: Meter. static func move_delta(forward: Vector3, right: Vector3, fwd_input: float, side_input: float, vert_input: float, speed_mps: float, delta_s: float) -> Vector3: diff --git a/src/editor/shortcut_keys.gd b/src/editor/shortcut_keys.gd index 48bc628..4c7c069 100644 --- a/src/editor/shortcut_keys.gd +++ b/src/editor/shortcut_keys.gd @@ -5,8 +5,9 @@ class_name ShortcutKeys ## Konvention (alle Shortcut-Karten): Tastaturcode-basiert — JEDER Action wird ## ein InputEventKey mit PHYSICAL keycode zugewiesen (KEY_*-Konstante). Die ## physische Taste liegt unabhängig vom Tastaturlayout an derselben Position -## (QWERTZ wie QWERTY bei W/A/S/D/E/Q/F), damit deutsche und Windows-Tastaturen -## identisch bedienen — keine OS-Hardcodes (PLAN.md Plattform-Regel). +## (QWERTZ wie QWERTY bei W/A/S/D/Leertaste/Shift), damit deutsche und +## Windows-Tastaturen identisch bedienen — keine OS-Hardcodes (PLAN.md +## Plattform-Regel). ## ## Registrierung per Code statt project.godot: KEY_*-Konstanten bleiben im ## Quelltext lesbar und der Test kann exakt dieselben Konstanten prüfen. @@ -23,9 +24,18 @@ const ACTIONS := { "editor_fly_back": [KEY_S], "editor_fly_left": [KEY_A], "editor_fly_right": [KEY_D], - "editor_fly_up": [KEY_E], - "editor_fly_down": [KEY_Q], - "editor_fly_fast": [KEY_SHIFT], + # Flug-Vertikal + Tempo (Shooter-Konvention, Sly-Wunsch 17.10.): + # Leertaste = hoch, Shift = runter (Half-Life/CS-Konvention), Strg halten = + # temporär schneller, Alt halten = langsamer. Mausrad bleibt Grundtempo. + # Konflikt-Freiheit: Leertaste/Shift/Strg SIND im Editor sonst nur als + # Modifier-Kombos belegt (Strg+Z, Umschalt+Klick …) — allein gedrückt + # triggern sie KEINE Werkzeug-Aktion (Tests: test_shortcuts.gd, + # Konflikt-Gruppe). Im Flugmodus gibt der Kamera-Einstieg den GUI-Fokus + # frei (orbit_camera._enter_fly), damit Leertaste keinen fokussierten + # Button auslöst. + "editor_fly_up": [KEY_SPACE], + "editor_fly_down": [KEY_SHIFT], + "editor_fly_fast": [KEY_CTRL], "editor_fly_slow": [KEY_ALT], # --- Auswahl / Objekt-Operationen ---------------------------------------- "editor_focus_selection": [KEY_F], diff --git a/src/ui/help_text.gd b/src/ui/help_text.gd index ec13af0..4997282 100644 --- a/src/ui/help_text.gd +++ b/src/ui/help_text.gd @@ -14,9 +14,9 @@ static func text() -> String: ["Mittlere Maustaste ziehen", "Orbit: um die Karte drehen"], ["Mittlere Maustaste + Umschalt ziehen", "Pan: Karte verschieben"], ["Mausrad", "Zoom (aus, während ein Objekt gedreht wird)"], - ["Rechte Maustaste GEDACHT halten", "Freelook-Flug: Umsehen + W vor / S zurück / A links / D rechts; E hoch / Q runter — funktioniert in JEDEM Werkzeug (Terrain, Bauen, Platzieren), die rechte Maustaste hat immer Vorrang"], - ["Umschalt (im Flug)", "schneller fliegen (×3)"], - ["Alt (im Flug)", "langsamer fliegen (×0,3)"], + ["Rechte Maustaste GEDACHT halten", "Freelook-Flug: Umsehen + W vor / S zurück / A links / D rechts; Leertaste hoch / Shift runter (Shooter-Konvention) — funktioniert in JEDEM Werkzeug (Terrain, Bauen, Platzieren), die rechte Maustaste hat immer Vorrang"], + ["Strg halten (im Flug)", "schneller fliegen (×3)"], + ["Alt halten (im Flug)", "langsamer fliegen (×0,3)"], ["Mausrad (im Flug)", "Grundgeschwindigkeit dauerhaft ändern (2–300 m/s)"], ["Umschalt+F", "Flugmodus dauerhaft an/aus"], ["F", "Auswahl ins Bild zentrieren"], diff --git a/src/ui/main.gd b/src/ui/main.gd index bddeae4..a435bb5 100644 --- a/src/ui/main.gd +++ b/src/ui/main.gd @@ -139,7 +139,7 @@ var _tree_select_lock := false # verhindert Rückkopplung Auswahl ↔ Baum func _ready() -> void: # InputMap-Actions der Tastatur-Shortcuts (Karte t_c46d31c2) — VOR UI/Kamera, - # damit die Kamera-Polls (Flug W/A/S/D/E/Q) die Actions sofort kennen. + # damit die Kamera-Polls (Flug W/A/S/D + Leertaste/Shift) sofort kennen. ShortcutKeys.register() manifest = _load_manifest() _build_ui() diff --git a/src/viewport/orbit_camera.gd b/src/viewport/orbit_camera.gd index 650d4c1..43e63c3 100644 --- a/src/viewport/orbit_camera.gd +++ b/src/viewport/orbit_camera.gd @@ -6,10 +6,12 @@ extends Camera3D ## Mausrad = Zoom — AUSSER ein Objekt/eine Platzier-Vorschau ## ist aktiv: dann dreht das Mausrad das Objekt ## (interaction.gd meldet das per wheel_filter). -## Rechte Maustaste GEDACKT = Freelook-Flug (Karte t_c46d31c2): Umsehen + +## Rechte Maustaste GEDACHT halten = Freelook-Flug (Karte t_c46d31c2): Umsehen + ## W vor / S zurück / A links / D rechts, -## E hoch / Q runter, Shift = schneller, -## Alt = langsamer. Mausrad ändert im Flug die +## Leertaste hoch / Shift runter (Shooter- +## Konvention, Sly-Wunsch 17.10.), +## Strg = schneller, Alt = langsamer. +## Mausrad ändert im Flug die ## GRUNDgeschwindigkeit dauerhaft. ## Umschalt+F (main.gd) = Freelook dauerhaft an/aus (Flug bleibt auch ## ohne gedrückte rechte Maustaste aktiv). @@ -146,7 +148,7 @@ func toggle_fly_lock() -> void: else: _fly_locked = true _enter_fly() - _status("Freelook-Flug DAUERHAFT an: W/S/A/D fliegen, E/Q hoch/runter, Umschalt schneller, Alt langsamer — Umschalt+F zurück zum Orbit.") + _status("Freelook-Flug DAUERHAFT an: W/S/A/D fliegen, Leertaste hoch / Shift runter, Strg schneller, Alt langsamer — Umschalt+F zurück zum Orbit.") ## Im Flugmodus? (interaction.gd blockt z. B. Mausrad-Objektdrehung dann ab.) @@ -183,7 +185,14 @@ func _enter_fly() -> void: _fly_yaw = yaw_deg _fly_pitch = pitch_deg _flying = true - _status("Freelook-Flug an: W/S/A/D fliegen, E/Q hoch/runter, Umschalt = schnell, Alt = langsam, Mausrad = Grund-Tempo. Rechte Maustaste loslassen = zurück zum Orbit.") + # Konflikt-Check (Sly-Wunsch 17.10.): Leertaste ist jetzt „hoch fliegen" — + # ein fokussierter Button würde Space/Enter als Klick interpretieren + # (Bibliothek-Button = Platzierung starten). Im Flug gehört die Leertaste + # der Kamera, also GUI-Fokus freigeben. + var vp := get_viewport() + if vp != null and vp.gui_get_focus_owner() != null: + vp.gui_release_focus() + _status("Freelook-Flug an: W/S/A/D fliegen, Leertaste hoch / Shift runter, Strg = schnell, Alt = langsam, Mausrad = Grund-Tempo. Rechte Maustaste loslassen = zurück zum Orbit.") func _exit_fly() -> void: diff --git a/tests/test_shortcuts.gd b/tests/test_shortcuts.gd index 05920b1..8001069 100644 --- a/tests/test_shortcuts.gd +++ b/tests/test_shortcuts.gd @@ -12,6 +12,7 @@ var checks := 0 func _init() -> void: ShortcutKeys.register() _test_inputmap_actions() + _test_fly_key_conflicts() _test_inputmap_actions_3() _test_policy_3() _test_fly_math() @@ -24,7 +25,7 @@ func _init() -> void: await process_frame _test_multi_delete() _test_focus_center() - _test_camera_fly_roundtrip() + await _test_camera_fly_roundtrip() # coroutine seit Sly 17.10. (Flug-Vertikal-Livetest) _test_rotate_90() _test_nudge_grid() _test_lift_multi() @@ -123,9 +124,9 @@ func _test_inputmap_actions() -> void: "editor_fly_back": KEY_S, "editor_fly_left": KEY_A, "editor_fly_right": KEY_D, - "editor_fly_up": KEY_E, - "editor_fly_down": KEY_Q, - "editor_fly_fast": KEY_SHIFT, + "editor_fly_up": KEY_SPACE, + "editor_fly_down": KEY_SHIFT, + "editor_fly_fast": KEY_CTRL, "editor_fly_slow": KEY_ALT, "editor_focus_selection": KEY_F, "editor_cancel": KEY_ESCAPE, @@ -196,6 +197,68 @@ func _test_inputmap_actions() -> void: _check(lift_floor, "editor_lift_to_floor → Strg+Bild↑") +# -------------------------------- Flug-Tasten Konflikt-Check (Sly 17.10.) ----- +## Sly-Wunsch 17.10.: Leertaste = hoch, Shift = runter, Strg = schneller. +## KONFLIKT-CHECK verpflichtend: die drei Tasten dürfen ALLEIN (ohne +## Kombos-Partner) KEINE Werkzeug-Aktion auslösen — weder über die InputMap +## (falsche Action) noch im Terrain-Werkzeug (Strg allein startet keinen +## Strich). Synthetische Events mit PHYSICAL keycode wie die echte Tastatur. +func _key_event(keycode: Key, shift := false, ctrl := false) -> InputEventKey: + var ev := InputEventKey.new() + ev.physical_keycode = keycode + ev.keycode = keycode + ev.shift_pressed = shift + ev.ctrl_pressed = ctrl + ev.pressed = true + return ev + + +func _matches(keycode: Key, action: String, shift := false, ctrl := false) -> bool: + return InputMap.event_is_action(_key_event(keycode, shift, ctrl), action, false) + + +func _test_fly_key_conflicts() -> void: + print("Testgruppe: Flug-Tasten Konflikt-Check (Leertaste/Shift/Strg)") + # Die neuen Flug-Actions feuern auf die EINZELNE Taste. + _check(_matches(KEY_SPACE, "editor_fly_up"), "Leertaste allein → editor_fly_up") + _check(_matches(KEY_SHIFT, "editor_fly_down"), "Shift allein → editor_fly_down") + _check(_matches(KEY_CTRL, "editor_fly_fast"), "Strg allein → editor_fly_fast") + # ... aber auf KEINE andere Editor-Action (alle verlangen Partner/andere Taste). + _check(not _matches(KEY_SPACE, "editor_fly_forward"), "Leertaste ≠ editor_fly_forward (W bleibt W)") + _check(not _matches(KEY_SPACE, "editor_help_overlay"), "Leertaste löst KEIN H aus") + _check(not _matches(KEY_SPACE, "editor_brush_raise"), "Leertaste schaltet KEINEN Pinselmodus") + _check(not _matches(KEY_SHIFT, "editor_freelook_toggle"), "Shift allein togglet NICHT den Flug (braucht F)") + _check(_matches(KEY_F, "editor_freelook_toggle", true), "Umschalt+F togglet den Flug weiter (unangetastet)") + _check(not _matches(KEY_SHIFT, "editor_save_as"), "Shift allein speichert NICHT (braucht Strg+S)") + _check(not _matches(KEY_SHIFT, "editor_redo_shift_z"), "Shift allein redoet NICHT (braucht Strg+Z)") + _check(not _matches(KEY_CTRL, "editor_undo"), "Strg allein macht KEIN Undo (braucht Z)") + _check(not _matches(KEY_CTRL, "editor_duplicate_selection"), "Strg allein dupliziert NICHT (braucht D)") + _check(not _matches(KEY_CTRL, "editor_lift_to_floor"), "Strg allein setzt NICHT auf den Boden (braucht Bild↑)") + _check(not _matches(KEY_CTRL, "editor_redo_y"), "Strg allein redoet NICHT (braucht Y)") + # Bestands-Combos unangetastet (Entf-Yolo etc.). + _check(_matches(KEY_Z, "editor_undo", false, true), "Strg+Z wirkt weiter (unangetastet)") + _check(_matches(KEY_DELETE, "editor_delete_selection"), "Entf wirkt weiter (unangetastet)") + # ECHTES Terrain-Werkzeug aktiv: Tasten-Events (Leertaste/Shift/Strg) dürfen + # keinen Strich starten — der Pinsel lebt nur von LMB (+Strg-Invert). + var res := 8 + var flat := PackedFloat32Array() + flat.resize(res * res) + var tfake := ProbeMain.new() + tfake.doc = {"terrain": { + "resolution": res, "size_x_m": 100.0, "size_z_m": 100.0, + "min_height_m": 0.0, "max_height_m": 10.0, + "heightmap_r16_b64": TerrainField.encode(flat, 0.0, 10.0), + }} + var ttool := Node3D.new() + ttool.set_script(load("res://src/editor/terrain_tool.gd")) + root.add_child(ttool) + ttool.setup(tfake, Camera3D.new(), ProbeBuilder.new(), null) + _check(bool(ttool.call("activate")), "Terrain-Werkzeug aktiviert (Test-Map)") + for keycode in [KEY_SPACE, KEY_SHIFT, KEY_CTRL]: + ttool.call("_unhandled_input", _key_event(keycode)) + _check(not bool(ttool.get("_stroking")), "Terrain: Leertaste/Shift/Strg ALLEIN starten KEINEN Strich") + + # ------------------------------------------------- Karte 3/3 (t_11b5c25b) ---- ## Karte 3/3: Datei & Undo, Werkzeugwechsel (F-Tasten), Hilfe + Test als @@ -259,14 +322,15 @@ func _test_policy_3() -> void: func _test_fly_math() -> void: print("Testgruppe: Freelook-Mathe (FlyMath)") _check(absf(FlyMath.speed_for(30.0, false, false) - 30.0) < 0.0001, "Tempo normal = Grund") - _check(absf(FlyMath.speed_for(30.0, true, false) - 90.0) < 0.0001, "Umschalt = ×3") + _check(absf(FlyMath.speed_for(30.0, true, false) - 90.0) < 0.0001, "Strg = ×3") _check(absf(FlyMath.speed_for(30.0, false, true) - 9.0) < 0.0001, "Alt = ×0,3") - _check(absf(FlyMath.speed_for(30.0, true, true) - 90.0) < 0.0001, "Umschalt dominiert bei beiden") + _check(absf(FlyMath.speed_for(30.0, true, true) - 90.0) < 0.0001, "Strg dominiert bei beiden") _check(absf(FlyMath.wheel_speed(30.0, true) - 34.5) < 0.0001, "Mausrad hoch: ×1,15 dauerhaft") _check(absf(FlyMath.wheel_speed(30.0, false) - 30.0 / 1.15) < 0.0001, "Mausrad runter: ÷1,15 dauerhaft") _check(FlyMath.wheel_speed(299.0, true) == FlyMath.SPEED_MAX_MPS, "Mausrad: Tempo-Grenze oben (300 m/s)") _check(FlyMath.wheel_speed(2.0, false) == FlyMath.SPEED_MIN_MPS, "Mausrad: Tempo-Grenze unten (2 m/s)") - # Bewegung: W fliegt in Blickrichtung (3D), A/D horizontal, E/Q weltachsig. + # Bewegung: W fliegt in Blickrichtung (3D), A/D horizontal, + # Leertaste/Shift weltachsig. var fwd := FlyMath.forward_from_euler(0.0, 30.0) # Blick nach -Z, leicht abwärts var right := FlyMath.forward_from_euler(0.0, 0.0).cross(Vector3.UP).normalized() var d := FlyMath.move_delta(fwd, right, 1.0, 0.0, 0.0, 10.0, 1.0) @@ -274,7 +338,7 @@ func _test_fly_math() -> void: var side := FlyMath.move_delta(fwd, right, 0.0, 1.0, 0.0, 10.0, 1.0) _check(absf(side.y) < 0.0001 and absf(side.x - 10.0) < 0.0001, "D straft horizontal rechts (kein Höhen-Anteil)") var up := FlyMath.move_delta(fwd, right, 0.0, 0.0, 1.0, 10.0, 1.0) - _check(absf((up - Vector3.UP * 10.0).length()) < 0.0001, "E hebt weltachsig (+10 m)") + _check(absf((up - Vector3.UP * 10.0).length()) < 0.0001, "Leertaste hebt weltachsig (+10 m)") var diag := FlyMath.move_delta(fwd, right, 1.0, 1.0, 0.0, 10.0, 1.0) _check(absf(diag.length() - 10.0) < 0.0001, "Diagonale normalisiert (√2-Falle umgangen)") _check(FlyMath.move_delta(fwd, right, 0.0, 0.0, 0.0, 10.0, 1.0) == Vector3.ZERO, "keine Eingabe → kein Delta") @@ -433,8 +497,15 @@ func _test_camera_fly_roundtrip() -> void: cam.call("_apply") var pos_before: Vector3 = cam.position _check(not bool(cam.call("fly_active")), "Orbit-Modus ist Default (kein Flug)") + # Konflikt-Check (Sly 17.10.): Flug-Einstieg gibt den GUI-Fokus frei — + # Leertaste darf keinen fokussierten Button auslösen (Space = ui_accept). + var fokus_btn := Button.new() + root.add_child(fokus_btn) + fokus_btn.grab_focus() + _check(root.gui_get_focus_owner() == fokus_btn, "Button hat GUI-Fokus (Ausgangslage)") cam.call("_enter_fly") _check(bool(cam.call("fly_active")), "RMT gedrückt → Flugmodus aktiv") + _check(root.gui_get_focus_owner() == null, "Flug-Einstieg gibt den GUI-Fokus frei (Leertaste gehört der Kamera)") cam.position += Vector3(25.0, -10.0, 5.0) # im Flug frei bewegt cam.call("_exit_fly") _check(not bool(cam.call("fly_active")), "RMT losgelassen → zurück zum Orbit") @@ -452,8 +523,27 @@ func _test_camera_fly_roundtrip() -> void: _check(bool(cam.call("fly_active")), "Umschalt+F: Dauerflug an (ohne RMT)") cam.call("toggle_fly_lock") _check(not bool(cam.call("fly_active")), "Umschalt+F nochmal: zurück zum Orbit") + # Neue Flug-Tasten (Sly 17.10.): Leertaste hebt, Shift senkt — live durch + # orbit_camera._process (Action-Strength-Poll, weltachsig). + cam.call("toggle_fly_lock") + var y0: float = cam.position.y + Input.action_press("editor_fly_up") + await process_frame + await process_frame + Input.action_release("editor_fly_up") + _check(cam.position.y > y0, "Leertaste hebt die Kamera im Flug (orbit_camera)") + var y1: float = cam.position.y + Input.action_press("editor_fly_down") + await process_frame + await process_frame + Input.action_release("editor_fly_down") + _check(cam.position.y < y1, "Shift senkt die Kamera im Flug (orbit_camera)") + cam.call("toggle_fly_lock") # Blick-Yaw-Accessor (Karte t_55257792): Orbit → yaw_deg, Flug → Flug-Yaw. - _check(absf(float(cam.call("view_yaw_deg")) - 30.0) < 0.0001, + # Toleranz 0,01°: jeder Flug-Exit rechnet yaw aus der Kamera-Basis zurück + # (look_at → euler_from_forward, float32) — der Sly-17.10.-Block oben macht + # einen Zyklus mehr, der Drift liegt bei ~2e-4° pro Roundtrip (unsichtbar). + _check(absf(float(cam.call("view_yaw_deg")) - 30.0) < 0.01, "view_yaw_deg im Orbit = Orbit-Yaw (30°)") cam.set("_fly_yaw", -120.0) cam.set("_flying", true)