Sly 25.10.: „weich“ soll ein Textur-Verlauf am Pinselrand sein, nicht fleckig;
gerade Strichkanten wirkten weiter hart (1-m-Übergang).
- Format (optional, alte Karten gültig): materials_b_r8_b64 (zweites Material
je Punkt) + materials_w_r8_b64 (Anteil 0–255); ohne Mischung fallen die
Felder weg. V15 prüft Länge/IDs/Paarigkeit.
- TerrainField.apply_material_brush_mix: innen voll, außen Smoothstep-Anteil;
nur verstärken, nie abschwächen; Gras drübermalen mischt zurück.
- Shader: Top-2 aus 4 Nachbarpunkten × (a, b) mit Anteilen → nur noch 2
Albedo-Fetches; Warp bleibt.
- Pinsel/Live-Vorschau, Spiegeln, Symmetrie-Analyse/-Anwenden, Resize,
Anheben, Undo (terrain-Snapshot) tragen die Misch-Schicht mit.
- Streu-Rand (vorheriger Commit) entfernt.
- Tests: test_mal_fein 30/30 (Verlauf fällt weich, nie abschwächen,
zurückmischen, Kodierung), Editor-Suite unverändert grün bis auf die
bekannten Umgebungs-Tests; Kette Editor → Server → Client per
run_map_download_e2e 15/15 mit gemischter Karte.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Neu shaders/selection_outline.gdshader: Inverted-Hull-Außenhülle (unshaded,
cull_front, Grow entlang der Normalen; Streifen-Uniform für den Zwilling).
Markierung jetzt über Outline-Kind am Mesh (Auswahl/Hover/Partner); Farben +
Breiten aus theme/editor_theme.tres (selection_outline*, *_width_mm) — eine
Quelle. material_override wird von der Markierung nicht mehr angefasst:
GLB-Texturen bleiben im Auswahl-Zustand voll sichtbar, der Tint bleibt beim
Markieren sichtbar (kein "Orange gewinnt" mehr).
Spawn-Zonen: konstante Disc-Deckkraft + dezenter Kontur-Ring (Torus) statt
Aufhellung/Emission; Label-Farbe aus dem Theme statt hartkodiertem Gelb.
Bau-Teile: Kontur um das Bauteil-Mesh statt halbtransparenter Vollbox.
Neu: set_hover()-API (schwächste Stufe) — Anschluss an die Mausbewegung
liegt in interaction.gd (Grenze dieser Karte) und folgt als eigene Karte.
Suite tests/test_selection_look.gd (41 checks: Theme-Quelle, Grundmaterial
unangetastet, Outline nur bei Markierung, restlos weg bei Abwahl, Zwilling
dünner+gestrichelt, Spawn-Ring, Bau-Teil-Kontur). Altsuiten an die neue
Semantik angepasst (test_glb_view: GLB-Auswahl ohne Override; test_map_tint
T3: Kontur statt Orange-gewinnt; test_object_mirror: Partner per Kontur).
Beweise: 4 Renderproben (fass, echtes GLB — vorher Vollorange / nachher
Kontur, je auf Sand und Fels) in /opt/data/scopedvr/work/u6_auswahl_beweise/.
Voll-Batterie 32/32 grün (EXIT 0) inkl. der drei angepassten Suiten.
Sly-Befund: Beim Heben/Senken über gemalten Sand/Fels verschwanden die
Material-Farben live (alles Gras), erst am Stroke-Ende kehrten sie mit
dem Vollrebuild zurück.
Root-Cause: terrain_tool rief im Live-Pfad (Heben/Senken/Glätten)
update_terrain(_heights) OHNE Material-Schicht auf — _mats wurde nur im
Malen-Modus dekodiert — und terrain_mesh fällt bei leerer Schicht auf
alles-Gras zurück (Legacy-Fallback).
Fix (zwei Ebenen):
- terrain_tool._refresh_from_doc dekodiert die Material-Schicht immer,
der Live-Stroke übergibt sie mit (Heben/Senken/Glätten wie Malen).
- terrain_mesh cached die zuletzt gesehene Schicht: Live-Updates ohne
mats-Param nutzen denselben Farb-Pfad wie der Idle-Rebuild; Legacy-v1
(nie gesehene Schicht) bleibt alles Gras — kein Phantom-Cache.
Test: tests/test_terrain.gd neue Gruppe „Live-Material-Farben" —
per-Frame-Check (5 Frames Heben über Sand-Zone, nicht nur Endzustand),
terrain_tool-Integration (Heben-Stroke übergibt Sand-Schicht, Stroke-Ende
= EIN Commit), Legacy-Gras ohne Phantom-Cache. Suite: 99 checks ALL OK;
alle weiteren Headless-Suiten grün (test_glb_view 12/22 FAIL ist
vorbestehend auf origin/main — GLB-Quellen fehlen im Workspace).
Root-Cause für Slys Gerätesymptom (dunkle Platte + Gitter, Terrain „wacht erst nach langem Drücken auf“): terrain_mesh.gd baute alle Dreiecke von oben CCW = Backface → komplette Deckfläche wurde von oben ge-cullt; sichtbar war nur der dunkle Sky-Boden (0.1/0.1/0.12) plus Gitter-Overlay. Gehobene Flanken waren nur sichtbar, wenn sie über die unsichtbare Deckfläche ragten (Rückenansicht) — daher „wirkt der Pinsel erst nach langem Drücken“.
- terrain_mesh.gd: Dreiecks-Indices gespiegelt [i, i+1, i+res, i+1, i+res+1, i+res] (CW von oben = Frontseite, Godot-Konvention); Normalen-Gradient korrigiert (x-Index läuft gegen Welt-X: n ∝ ((hx_r-hx_l)·cell_z, 2·cell_x·cell_z, (hz_u-hz_d)·cell_x)) — Hänge waren vorher falsch beleuchtet. MeshInstance lazily erzeugt (update_heights sicher vor _ready).
- map_view_builder.gd: Gitter default AUS über sichtbarem Terrain (grid_node), ohne Terrain-Block als Boden-Referenz sichtbar; set_grid_visible()/grid_is_visible() ohne Rebuild.
- main.gd: Toggle-Knopf „⊞ Gitter“ in der Top-Bar; Nutzer-Wahl überlebt jeden Viewport-Rebuild.
- Erster Pinselkontakt: mit sichtbarem Terrain wirkt der Live-Pfad ab Frame 1 (code-seitig korrekt, war nur unsichtbar).
Tests: test_terrain 64 Checks (NEU: Winding aller Dreiecke = Frontseite von oben, Normale h=0 = (0,1,0), X/Z-Hügel-Normale nicht invertiert, erster RAISE-Druck verschiebt Vertex messbar im Mesh, Gitter-Default/Toggle/Boden-Referenz). Suite 441 Checks + Export-Suite 21 grün; --import ohne Parse-Error.
- 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]
TerrainField (src/svmap/terrain_field.gd): Höhenfeld-Mathematik auf dekodiertem
PackedFloat32Array — Pinsel mit Kosinus-Falloff (RAISE/LOWER/SMOOTH, zweiphasiges
3x3-Glättung), R16-Encode (uint16 LE row-major), ray-march+Bisektion-Cursor auf
dem GELÄNDE (nicht Basis-Ebene). EDITOR_MAX_RESOLUTION=129 (Editor-MVP-Perf-Limit,
Format bleibt 17-513 für Interop).
TerrainMeshView (src/viewport/terrain_mesh.gd): Höhenfeld als ArrayMesh mit
Höhen-Farbverlauf (Vertex-Farben); update_heights schreibt Vertices live neu
während des Strichs (res 129 = 16,6k Punkte, flott). Ersetzt die flache
Boden-Plane in map_view_builder (rebuild + rebuild_terrain_from_doc + update_terrain).
TerrainTool (src/editor/terrain_tool.gd): Pinsel-Modus im Viewport — Linksklick-
Ziehen formt (Heben/Senken/Glättten nach Panel-Wahl), Rechtsklick-Ziehen das
Gegenteil (kein Umschalten nötig). Pinsel-Ring (Torus) folgt dem Cursor auf dem
Terrain, farbcodiert pro Modus. GANZER Strich = EIN ActionHistory-Eintrag
(snapshot terrain VOR dem Strich, commit NACH dem Encode). Range-Expansion:
min/max_height_m weiten sich automatisch auf, wenn der Pinsel drüber hinaus will
(sonst wäre Senken auf flacher Map unmöglich) — Spannweite ≤ 500 m (V6), Undo
stellt die Range mit zurück. Doc wird nur ZUM Strich-Ende verändert.
main.gd: Werkzeug-Button 'Terrain formen' (groß, deutsch), Terrain-Panel mit
Heben/Senken/Glättten-Buttons + Pinsel-Größe/Stärke-Slider mit Live-Werten,
Modus-Wechsel objects<->terrain (Bibliothek-Klick wechselt zurück, ESC beendet),
interaction.gd ruht im Terrain-Modus. Performance-Zähler in der Statusleiste:
Terrain-Grid + Dreiecke (+ % vom Quest-3-Budget 524.288) + Editor-FPS.
Demo-Hook --terrain-demo [ordner]: 5 Szenarien inkl. Undo-Roundtrip als PNGs.
Neue Maps: res 129 (vorher 257) — erfüllt das Editor-MVP-Perf-Limit automatisch.