Flugmodus-Tasten: Leertaste=hoch, Shift=runter, Strg=schneller (Sly 17.10., Shooter-Konvention)

- 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).
This commit is contained in:
Hermes (godot-vr) 2026-10-17 17:45:37 +00:00
parent d6c472603f
commit db6bcec971
8 changed files with 148 additions and 33 deletions

View file

@ -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`).

View file

@ -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)

View file

@ -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:

View file

@ -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],

View file

@ -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"],

View file

@ -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()

View file

@ -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:

View file

@ -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)