scopedvr-map-editor/README.md
Hermes (godot-vr) db6bcec971 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).
2026-10-17 17:45:37 +00:00

342 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ScopedVR Map Editor
Standalone-PC-App zum Bauen von ScopedVR-Maps. Technisches Fundament: Godot 4.x
(MIT-Lizenz, lizenzfrei, keine Fremd-Abhängigkeit). Creator sehen/brauchen
Godot NIE — die App ist das Produkt (PLAN.md, Entscheidung 14).
## ⭐ Chef-Grundsatz (Sly, 15.10.): MAXIMALE NUTZERFREUNDLICHKEIT
> „Der soll auf jeden Fall so nutzerfreundlich sein, wie es nur geht, damit
> echt richtig easy ist Maps zu bauen und es auch Spaß macht!"
Konkret heißt das für JEDE Editor-Karte:
- Deutsch, große Buttons, Klartext-Beschriftungen — kein Fachchinesisch.
- Klick statt Konsole: gewünschte Aktion per Maus erreichbar (ziehen, drehen,
klicken), ohne Tastenkürzel auswendig zu lernen (Kürzel dürfen optional sein).
- Sofortiges visuelles Feedback (Objekt leuchtet beim Anfassen, Vorschau
vor dem Platzieren, Undo mit STRG+Z).
- Die App hilft: ungültige Aktionen erklären WARUM („Spawn-Zone fehlt noch
für Team Rot — klicke auf den Boden, um sie zu setzen").
- Test-Benchmark: Sly baut ohne Anleitung in < 10 Minuten eine kleine Map.
## Philosophie (Entscheidung 20 — GESCHLOSSENER Baukasten)
- Objekte kommen ausschließlich aus der Spiel-Asset-Bibliothek (geprüfte Poly-Budgets).
- KEINE freien Mesh-Imports. Konsistenz + Performance statt unbegrenzter Freiheit.
- Performance-Prüfung ist eingebaut (Objekt-Count-/Poly-Limits), nicht optional.
## MVP-Werkzeuge (Reihenfolge = Karten-Kette)
1. **Terrain** — Höhenfeld bearbeiten (heben/senken/glätten), Sniper-Distanzen 100–800 m (Entscheidung 7).
2. **Objekt-Platzierung** — Bibliothek-Browser, platzieren/drehen/verschieben.
3. **Spawn-Zonen** — pro Team (TDM), Validierung gegen Regeln.
4. **Map-Metadaten** — Name, Autor, Version, Wetter-Preset (mittag/abend/regen/nacht, siehe Weather-System 9ebe8ef/a296986).
5. **Performance-Check** — Objekt-Count/Poly-Budget gegen die FPS-Baseline.
6. **Export** — `.svmap`-Paket (Format: docs/map_format.md im scopedvr-match-server Repo, Karte t_74976e9f).
## Architektur (MVP)
- Godot 4.x Desktop-App (die Editor-UI IST Godot-Control-UI — pragmatisches MVP).
- Map-Datenmodell = 1:1 das svmap-Format (kein internes Zwischenformat → kein Konverter).
- Speichern/Laden direkt auf .svmap; Export = Kopie + Validierung.
## Milestone-Zuordnung
M2 (siehe PLAN.md §6): Map-Paket-Format → Editor-MVP → erste offizielle Map
(offenes Gelände) → Map-Repo/Release-Workflow über Forgejo (M3).
## Status (M2, Karte t_40d855d0 — UX-Fundament FERTIG)
UX-Fundament auf dem App-Skelett (t_5badf371), nach dem Chef-Grundsatz gebaut:
*(Unverändert darunter: svmap laden/speichern V1–V14 mit Prüfsummen-Interop,
Neue-Map-Dialog mit 4 Pflicht-Spawns pro Team, Orbit-Viewport mit Gitter/
Boden/Wetter-Stimmung — siehe Commit 84bc081 und `svmap_io.gd`.)*
- **Undo/Redo (`src/editor/action_history.gd`):** zentrale ActionHistory über
die svmap-Ober-Blöcke (`objects`/`terrain`/`spawn_zones` — tiefe Kopien vor
und nach jeder Änderung, merge_tags gegen Mausrad-Tick-Fluten, Max-Stufe 100).
Strg+Z / Strg+Y UND große Buttons „↩ Rückgängig / ↪ Wiederholen" oben.
Spätere Werkzeuge melden sich mit `snapshot(keys)` vor und
`commit(desc, before, after)` nach der Änderung an — Terrain-Strokes und
Spawn-Zonen landen damit automatisch in derselben History.
- **Objekt-Platzierung mit Ghost-Preview (`src/editor/interaction.gd`):**
Bibliothek links (deutsche Namen + Poly-Zahl), Klick startet den
Platzier-Modus — eine transparente Vorschau schwebt am Cursor auf dem Boden
(Höhe kommt aus dem Terrain), Klick setzt, ESC bricht ab (Rechte
Maustaste = Flug, das Objekt bleibt in der Hand — Karte t_2470de89),
Mausrad dreht die Vorschau VOR dem Setzen (15°-Raster). Modus bleibt an
für schnelles Mehrfachsetzen. Poly-Budget wird VOR dem Platzieren geprüft.
- **Auswahl-Anfassen:** Klick wählt ein Objekt (orange + Emission), Ziehen
verschiebt es (auf Terrain-Höhe geclampt in der Map), R-Taste ODER Mausrad
dreht (15°-Raster, Shift+R zurück), Entf löscht. Auch über den Objekt-Baum
wählbar (beide Wege bleiben synchron). Panel „Auswahl“ rechts zeigt Position/
Richtung + Bedienhinweise.
- **Helfende Meldungen:** Statusleiste unten erklärt nach jeder Aktion den
NÄCHSTEN Schritt („Klicke in die Karte“, „Strg+Z holt es zurück“,
Budget-Warnung ab 80 %). Fehler-Dialoge bleiben Klartext, keine Godot-Texte
im sichtbaren UI.
- **Deutsche, große UI:** Schrift 16 px Basis (≥ 14 px überall), Buttons/
Klickflächen ≥ 40 px Höhe, Mausrad-Konflikt gelöst: Rad dreht Objekt/
Vorschau, wenn eines aktiv ist — sonst zoomt es die Kamera.
- **Tests:** `tests/test_ux.gd` (History-Roundtrips setzen/löschen/verschieben/
drehen, Merge-Tag-Verhalten, Alias-Sicherheit der Snapshots, Kapazität,
Terrain-Höhen-Abfrage, Platzhalter-Meshes).
Lauf: `godot --headless --path . --script res://tests/test_ux.gd`
**Bekannte Falle (15.10. live passiert):** Nach `git pull` mit NEUEN
`class_name`-Skripten (z. B. `action_history.gd`) muss einmal
`godot --headless --import` laufen, bevor die GUI gestartet wird — sonst ist
der globale Klassen-Cache (`.godot/`) alt, `main.gd` wirft „Identifier not
declared" und das Fenster startet ohne UI. Symptom: Parse-Errors im App-Log
trotz grünem Test-Lauf (der ja selbst mit importiertem Cache lief).
**Bibliothek:** `library/objects.json` ist die Editor-Kopie der offiziellen
Registry aus dem Match-Server-Repo — neue Objekte DORT registrieren und hier
synchron ziehen (IDs sind der Vertrag, Entscheidung 20).
## Status (M2, Karte t_3a998ae4 — Objekt-Bibliothek FERTIG)
Auf dem Terrain-Werkzeug (t_9aba04f1) aufgebaut, nach dem Chef-Grundsatz:
- **Bibliothek-Browser mit Gruppen (`src/editor/library_browser.gd`):** Die
11 etablierten Objekte aus `library/objects.json`, sortiert in deutsche
Klapp-Gruppen (Kisten & Barrieren / Bauwerke / Natur + automatisch
„Sonstiges" für neue, noch ungruppierte IDs). Jeder Button zeigt ein
128-px-Thumbnail (`library/thumbs/*.png` — einmalig mit
`xvfb-run -a godot4 --rendering-driver opengl3 --path . --script
res://tools/gen_thumbs.gd` gerendert und committed; fehlt ein PNG, fällt
der Button sauber aufs Text-Layout zurück). Deutsche Namen wohnen jetzt in
`LibraryBrowser` (ohne UI → headless testbar).
- **Snap to Ground (`src/editor/object_ops.gd`):** Ein Klick auf
„⚡ Alles auf den Boden setzen" holt ALLE schwebenden Objekte auf die
Terrain-Höhe (z. B. nach dem Terrain-Formen) — EIN Undo-Eintrag für die
ganze Aktion. Objekte, die schon auf dem Boden stehen, bleiben unangetastet.
- **Raster-Snap (1 m):** CheckButton in den Objekt-Werkzeugen — beim
Platzieren UND Ziehen rasten X/Z auf ganze Meter (Höhe folgt weiter dem
Terrain). AUS = freies Platzieren wie bisher.
- **svmap-Roundtrip:** Objekte (Bibliotheks-ID + Pos/Rot/Scale) überleben
Speichern/Laden identisch, save→load→save bleibt byte-identisch
(`tests/test_library.gd`).
- **Tests:** `tests/test_library.gd` (Bibliothek laden, Gruppen komplett/
überschneidungsfrei, Thumbnails 128×128, Raster-Snap, Snap-to-Ground + je
Undo für setzen/drehen/verschieben/löschen über ALLE 11 Objekte,
svmap-Roundtrip mit Objekten + Poly-Budget aus dem Katalog).
- **Desktop-Smoke:** `godot … -- --library-demo [ordner] [--library-demo-quit]`
fährt Bibliothek/Platzierung/Schweben/Snap programmatisch durch und
speichert Screenshots.
**Noch offen (folgende Karten der MVP-Kette):** Performance-Check-UI,
Export-Workflow.
## Status (M2, Karte t_c4162c5d — Spawn-Zonen FERTIG)
Auf der Objekt-Bibliothek (t_3a998ae4) aufgebaut, nach dem Chef-Grundsatz:
- **Spawn-Zonen malen (`src/editor/spawn_tool.gd`):** Werkzeug
„🚩 Spawn-Zonen malen“ — Team Blau 🔵 / Team Rot 🔴 (TDM-Konvention:
IDs A…/B…, mapformat § spawn_zones), Klick setzt eine farbige flache
Kreis-Zone auf dem Terrain (Vorschau-Kreis folgt der Maus), Radius
2–30 m per Regler. Zone anklicken → auswählen + ziehen (Terrain-Höhe
bleibt), Entf löscht, Strg+Z nimmt JEDE Operation zurück (jede Zone =
EIN History-Eintrag, Radius-Slider-Ticks sind merge-gekoppelt).
- **Live-Validierung (`src/editor/spawn_ops.gd`):** Block „Spawn-Check“
rechts bleibt STÄNDIG sichtbar und spiegelt exakt die Loader-Regeln
(V11: ≥ 4 Spawn-Punkte pro Team · V7: Objekt-IDs in der Bibliothek) —
z. B. „✅ Spawns Team Blau: 4 · ❌ Spawns Team Rot: 2 (mindestens 4
nötig)“ + Klartext-Hinweis, WIE die Zone ergänzt wird. Fehler zeigt der
Editor VOR dem Export, nicht erst beim Speichern.
- **Map-Fertig-Check:** Button „✅ Karte prüfen“ oben — Klartext-Liste
✅/❌ (Name, Autor, beide Teams, Objekt-IDs, Poly-Budget) in einem
Fenster; fehlt etwas, wackelt (Shake-Tween) der Button und die
Statusleiste sagt „Map ist noch nicht bereit — siehe Warnungen“.
Der harte Gate bleibt das Speichern (`SvmapIO.save_to_file` —
dieselbe `_validate_core`-Funktion, EINE Implementierung, kein Drift).
- **Export-Signierung:** unverändert der eine Code-Pfad — Editor-Speichern
ruft `SvmapIO.sign_map` (SHA-256 über kanonisierte Payload). Abnahme mit
ECHTEM Server-Loader: Editor-Export durch `SvmapLoader.parse_and_validate`
aus scopedvr-match-server gejagt → ok=true inkl. V13-Prüfsummen-Check
(kein Copy-Paste-Drift, Interop bewiesen).
- **Radius-Persistenz:** Der Zonen-Radius ist KEIN Formatfeld (spawn_zones
speichert Punkte) — er wohnt als `design_notes.spawn_radius_m` in der
Map (Loader prüft design_notes nicht → rundtrip-sicher). Zonen sitzen
AUF dem Terrain (`terrain_height_at`), egal wie hügelig die Map ist.
- **Tests:** `tests/test_spawns.gd` (52 Checks: setzen/ändern/löschen +
je Undo/Redo mit Radius, Merge-Tags, Live-Status ✅/❌, ready_report
grün/rot, Export→Re-Import identisch + byte-identischer Roundtrip,
Zone auf Terrain-Höhe). Parse-Check: 23 Scripts, 0 Fehler.
- **Desktop-Smoke:** `godot … -- --spawns-demo [ordner] [--spawns-demo-quit]`
fährt Zonen-Übersicht → Live-Check-Alarm → Undo → „Karte prüfen“
programmatisch durch und speichert Screenshots.
## Status (M2, Karte t_7567a06f — Objekt-Ops: Duplizieren + Hoch/Runter + Spiegeln)
Auf dem UX-Fundament (t_40d855d0) und der Objekt-Bibliothek (t_3a998ae4)
aufgebaut, nach dem Chef-Grundsatz (Klick statt Konsole, deutsche UI,
Undo für alles):
- **Duplizieren (`⧉ Duplizieren`-Button + Strg+D):** Die Kopie des
ausgewählten Objekts hängt +2 m in X versetzt daneben (Rasterversatz,
Karte t_c46d31c2; Füßpunkt-Höhe unverändert, mit Raster-Snap eingerastet,
wenn an) und ist DIREKT angewählt — sie lässt sich gleich
weiterziehen/drehen/löschen. Drehung, Größe und Höhe werden übernommen.
Bei Mehrfachauswahl werden ALLE Objekte kopiert (EIN Undo-Schritt).
Poly-Budget und Objekt-Limit werden VOR dem Kopieren geprüft
(Klartext-Meldung, was zu tun ist). EIN Undo-Schritt.
- **Anheben/Absenken (`▲ Anheben / ▼ Absenken` + Bild↑/Bild↓):** Objekte
ändern die Höhe in 0,25-m-Schritten. MEHRERE Klicks in Serie = EIN
Undo-Schritt. Design-Entscheidungen, dokumentiert:
- Die Höhe wohnt direkt in `object.pos.y` — das svmap-Format erlaubt
freie y-Werte innerhalb der Match-Grenzen (Loader-Regel V9), deshalb
ist **KEIN Extra-Feld im Dateiformat nötig** und die Loader-Kompatibilität
bleibt unangetastet (bewiesen in `tests/test_ux.gd`: Erhöhtes Objekt
überlebt gespeichern → laden intakt (Höhe bleibt exakt)).
- **Absenken stoppt an der Terrain-Höhe** am Objektstandort — nichts wird
in Hügel begraben. Steckt ein Objekt im Boden (z. B. nach dem
Terrain-Formen), bewegt Absenken es NICHT, und die Statusleiste erklärt,
dass `▲ Anheben` oder `⚡ Alles auf den Boden setzen` hilft.
- **Anheben** hat als Obergrenze die Match-Grenze `bounds.max.y` — höher
passt der Server nicht (V9).
- **Ziehen behält die Höhe:** Ein angehobenes Objekt schwebt beim
Verschieben in derselben Höhe über dem (neuen) Boden weiter; bodennah
bleibt bodennah.
- Die Auswahl-Anzeige zeigt die Höhe mit („+0,25 m über dem Boden“).
- **Spiegeln (`⇄ Links↔Rechts` / `⇅ Vor↔Zurück`):** Das Objekt wird AN
SEINER STelle gespiegelt — Position bleibt, die Blickrichtung dreht:
- links↔rechts (Welt-X-Spiegelung): `rot_y_deg → −rot`
- vor↔zurück (Welt-Z-Spiegelung): `rot_y_deg → 180° − rot`
(Herleitung im Quellcode: gespiegelter Richtungs-Vektor = gespiegelter
Winkel; jede Spiegelung ist dadurch selbstinvers — zweimal spiegeln =
Ausgangszustand.) Die Rotation wird mitgespiegelt, wie bestellt.
Bewusst NICHT um die Kartemitte gespiegelt: das würde das Objekt quer
über die Map springen lassen — überraschend für Einsteiger. Klartext
in Buttons + Tooltip.
- **Alles über ActionHistory:** Snapshot vor/nach JEDE Aktion
(Duplizieren = EIN Schritt, Anheben-Serie = EIN Schritt via Merge-Tag,
Spiegeln = EIN Schritt). Statusleiste erklärt nach jeder Aktion den
nächsten Schritt und die Grenzen auf Deutsch.
- **Tests:** `tests/test_ux.gd` auf 90 Checks erweitert (42 neu):
Duplizieren (Raster/ohne Raster/Undo-Roundtrip/Index-Schutz), Anheben/
Absenken (Schrittweite, Boden-Stopp, Deckel bounds.max, versteckt-Regel,
Merge-Tags), Spiegeln (X/Z-Mapping, Selbstinvers, Position unverändert),
svmap-Roundtrip mit angehobener Höhe. `godot … --quit-after` smoke:
UI baut alle neuen Buttons fehlerfrei.
## Status (M2, Karte t_f6f29d86 — Auswahl-System FERTIG)
Mehrfachauswahl + Gruppieren + Gruppen-Verschieben, auf dem UX-Fundament
(t_40d855d0) aufgebaut, nach dem Chef-Grundsatz:
- **Mehrfachauswahl (`src/editor/interaction.gd`):** Umschalt+Klick fügt
ein Objekt hinzu/entfernt es, Rahmen-Aufziehen (Rubberband) über leeren
Boden wählt ALLE Objekte im Rahmen (Umschalt + Rahmen = zusätzliche zur
Auswahl). Loser Klick auf leere Fläche hebt die Auswahl auf. ALLE
markierten Objekte färben sich orange, das Auswahl-Panel rechts listet
sie namentlich auf (ab 9: „… und N weitere“). Projektions-Mathe liegt
in `src/editor/selection_math.gd` (headless getestet).
- **Gruppieren:** Buttons „🔗 Gruppieren / ⛓ Gruppe lösen“ im Auswahl-Panel
+ Kürzel Strg+G / Strg+Umschalt+G. Eine Gruppe braucht ≥ 2 Objekte.
Gruppen-Mitglieder ziehen und drehen GEMEINSAM — ein Griff an ein
Gruppen-Mitglied nimmt automatisch die ganze Gruppe (und markiert sie),
die relative Anordnung bleibt exakt erhalten. Drehen geschieht um den
Gruppen-Mittelpunkt. „Gruppe lösen“ befreit ALLE Mitglieder (auch die
nicht mitgewählten), Strg+Z baut sie wieder zusammen.
- **ENTSCHIEDUNG Gruppen-Persistenz:** Gruppen-Zugehörigkeit liegt als
OPTIONALES Objekt-Feld `"group"` (int ≥ 0, abwesend = ungruppiert)
DIREKT im svmap-Format — nicht als Editor-Sitzungsstatus. Damit
überleben Gruppen Speichern/Laden UND Undo/Redo (ActionHistory kopiert
den objects-Block tief, das Feld reist mit). Loader-Kompatibilität
nachgewiesen (scopedvr-match-server `src/maps/svmap_loader.gd`):
V4 prüft nur Pflichtfelder, unbekannte Objekt-Keys werden nicht
abgelehnt, die SHA-256-Prüfsumme läuft über das ganze kanonisierte
Dict (Feld reist automatisch mit). Alte Maps ohne Feld bleiben 1:1
ladbar, `format_version` bleibt 1 — KEIN Version-Bump, KEINE
Server-Loader-Änderung nötig. Format-Doku-Anpassung (map_format.md im
Server-Repo) als Folge-Karte.
- **Gruppen-Verschieben + Undo:** Ziehen einer Mehrfachauswahl oder Gruppe
bewegt ALLE beteiligten Objekte (Terrain-Clamp PRO Objekt: X/Z bleiben
in der Map, Y = Terrain-Höhe an der neuen Stelle) — EINE Aktion =
EIN ActionHistory-Schritt (auch gemeinsames Drehen, Mausrad-Ticks
derselben Auswahl sind merge-gekoppelt). Löschen löscht die ganze
Auswahl in EINEM Schritt.
- **Tests:** `tests/test_ux.gd` erweitert auf 52 Checks (Gruppen-
gruppieren/lösen/IDs, gemeinsames Verschieben mit Terrain-Clamp,
Drehen um die Mitte mit Anordnungs-Erhalt, EIN-Schritt-Undo/Redo,
svmap-Roundtrip inkl. Prüfsumme + alte-Map-Kompatibilität,
Rubberband-Projektion inkl. Kamera-Rückseiten-Filter). Komplette
`run_tests.sh`-Suite grün (206 Checks).
- **Desktop-Smoke:** `godot … -- --groups-demo [ordner] [--groups-demo-quit]`
fährt Mehrfachauswahl → Gruppieren → gemeinsames Ziehen/Drehen →
Undo programmatisch durch und speichert Screenshots.
## Karte testen (M2, Karte t_b84af40f — FERTIG)
Button „▶ Karte testen“ oben (auch im Datei-Menü) startet die AKTUELLE Map —
auch ungespeicherte! — sofort als Spiel in einem EIGENEN Fenster. Der Editor
bleibt offen. Im Spiel: WASD laufen, Maus umsehen, Shift sprinten, Leertaste
springen, ESC beendet das Test-Fenster sauber (kein Prozess-Kill nötig).
- **Temp-Export (`src/editor/test_run.gd`):** Die Map wird nach
`user://testrun/<name>.svmap` exportiert (deterministischer Name aus dem
Map-Namen, erneuter Testlauf überschreibt) — über dieselbe
`SvmapIO.save_to_file`-Validierung wie das echte Speichern. Ist die Map
noch nicht lauffähig (z. B. Spawn-Zonen fehlen), kommt der Klartext-Block
mit den fehlenden Punkten, GESTARTET wird nichts.
- **Client-Suche (erste Treffer-Zeile gewinnt):**
1. Konfigurationsdatei `user://scopedvr_editor.cfg`:
```ini
[test]
client_dir="/pfad/zu/scopedvr-client"
godot_path="/pfad/zu/godot" ; nur nötig, wenn der Editor NICHT über Godot läuft
```
2. Klon neben dem Editor: `<Editor-Ordner>/../scopedvr-client`
(erkennbar an `project.godot`).
- **Start:** Der Client läuft als eigener Godot-Prozess
(`godot --path <client> -- --testmap <map.svmap> --mock-master`) — kein
Master, kein Match-Server, alles lokal. Godot-Binary = das, unter dem der
Editor selbst läuft.
- **Tests:** `tests/test_testrun.gd` (14 Checks: Temp-Export mit Roundtrip,
ungültige Map blockiert, Client-Suche inkl. Konfig-Vorrang, Godot-Findung,
exakte Argumentliste). Parse-Check umfasst `test_run.gd`.
## Status (M2, Karte t_c46d31c2 — Tastatur-Shortcuts 1/3: Navigation + Auswahl + Löschen FERTIG)
Tastenkürzel-Karte 1 von 3 (Sly-Freigabe 16.10., finale Belegung).
**`docs/tasten.md` ist jetzt die deutsche Source of truth** für ALLE
Shortcut-Karten — Karten 2/3 ergänzen dort.
- **Konventionen (alle Karten):** Tastaturcode-basiert — InputMap-Actions mit
PHYSISCHEN `KEY_*`-Konstanten (`src/editor/shortcut_keys.gd`, registriert in
`main._ready`, idempotent). Physische Codes = deutsche (QWERTZ) und
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 + 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`).
Konflikt-Auflösung: RMB hat ABSOLUTEN Vorrang für den Flug — in JEDEM
Werkzeugzustand (Terrain, Bauen, Spawn, Platzieren; Karte t_2470de89,
kein `rmb_filter` mehr), Mausrad im Flug gehört dem Tempo (interaction
blockt).
- **F** = Auswahl ins Bild zentrieren — Orbit-Ziel wandert auf den Auswahl-
Mittelpunkt (Anker + halbe Mesh-Höhe, `SelectionMath.focus_center`); im
Flug dreht sich nur die Blickrichtung.
- **Esc** = Prioritätskette Werkzeug verlassen → Ghost-Preview abbrechen →
Auswahl aufheben, als pure, headless-getestete Funktion
(`src/editor/shortcut_policy.gd`).
- **Entf (DELETE)** = Auswahl SOFORT löschen ohne Rückfrage (Sly-Yolo) —
wirkt auf Szene-Auswahl UND Objektbaum-Markierung (Baum und Szene sind
synchron), Strg+Z deckt es ab. War schon mehrfach-fähig, jetzt
getestet (EIN Undo-Schritt holt ALLE zurück).
- **Strg+D** = Auswahl duplizieren: Kopie(n) +2 m in X (Rasterversatz),
Füßpunkt unverändert; bei Mehrfachauswahl ALLE Objekte mit EINEM
Undo-Schritt, Budget/Limit gesammelt VORHER geprüft
(`ObjectOps.duplicate_objects`).
- **Strg+LMB** = Mehrfachauswahl umschalten (Objektbaum: SELECT_MULTI +
native Strg+Klick-Logik, spiegelt sich in die Viewport-Auswahl und
umgekehrt). Bestandsaktionen (Löschen, Duplizieren, Ziehen, Gruppieren)
wirken auf die GANZE Mehrfachauswahl.
- **Tests:** `tests/test_shortcuts.gd` NEU (70 Checks: InputMap-Actions
inkl. Idempotenz, Freelook-Mathe, Esc-Prioritäten, Duplizieren +2 m X
mit Undo-Roundtrip, Entf-Mehrfachauswahl gegen die ECHTE interaction.gd,
Focus-Mittelpunkt, Kamera Flug-Roundtrip). Alle 8 Suiten grün
(571 Checks), Builder-Export 21/0, Parse-Check aller geänderten Scripts
(`tools/parse_check_shortcuts.sh`).