Der Off-by-one im Test erwartete nach 2 Undos den Zustand nach move1,
obwohl 3 Züge davorliegen (Zug, Kanten-Clamp, zurück ins Feld). Der
Code war korrekt (EINE Aktion = EIN Undo-Schritt), der Test zählte
falsch. Jetzt wird jeder Schritt einzeln verifiziert inkl. geclamptem
Kanten-Zustand. Suite: 253 Checks grün.
- Mehrfachauswahl: Umschalt+Klick addiert/entfernt, Rahmen-Aufziehen über
den Viewport wählt alles im Rahmen (SelectionMath, headless getestet);
alle Markierten leuchten orange, Panel listet sie namentlich.
- Gruppieren/Lösen: Buttons im Auswahl-Panel + Strg+G / Strg+Umschalt+G;
Gruppe zieht und dreht GEMEINSAM um die eigene Mitte, Anordnung bleibt.
- ENTSCHIEDUNG Persistenz: optionales Objekt-Feld "group" (int >= 0)
direkt im svmap-Format — Loader-Kompatibilität nachgewiesen
(Server-Loader prüft nur Pflichtfelder; Prüfsumme läuft über das ganze
Dict), alte Maps bleiben ladbar, format_version bleibt 1, kein
Server-Loader-Change nötig. Gruppen überleben Save/Load UND Undo/Redo.
- Gruppen-Verschieben: Terrain-Clamp pro Objekt, EINE Aktion = EIN
ActionHistory-Schritt (auch Drehen/Mehrfach-Löschen).
- Tests: test_ux.gd auf 52 Checks erweitert (Gruppen, Undo-Ein-Schritt,
svmap-Roundtrip + Alte-Map-Kompatibilität, Rubberband-Projektion);
run_tests.sh komplett grün (206 Checks).
- Desktop-Smoke: --groups-demo [ordner] [--groups-demo-quit]
Karte t_7567a06f. Hoehe wohnt direkt in object.pos.y — KEIN svmap-Format-
Feld noetig (V9 erlaubt freie y-Werte in bounds; Server-Loader geprueft).
Absenken stoppt am Terrain (nichts begraben), Anheben deckelt auf
bounds.max. Spiegeln am Objekt selbst (Position bleibt): X → −rot,
Z → 180°−rot selbstinvers. Alles über ActionHistory (Merge-Tags für
Lift-Serien), deutsche UI + Klartext-Status, dragt behält Terrain-Abstand.
test_ux.gd: 90 Checks (42 neu), Suite 5/5 grün, xvfb-Smoke UI-Bau ok.