- BauKatalog: 12 Teile (Fundament, Boden, Wände mit Tür/Fenster/Scharte, Brüstung, Pfeiler, Giebel, Dächer, Treppe) auf Basis der CC0-Geometrie des Modular Builder 0.2.0, Wandhöhe 3 m
- BuildSnap: Wände auf Zellkanten, Böden in Zellmitte, Höhe aus Ebenen statt Klickhöhe, Ersetzen (Tür in Wand)
- Gebäude kopieren/auswählen/löschen im Bauen-Tab, Kopien bekommen eigene Gruppe
- Kollision serverkonsistent über Trimesh-Dateien (Türen/Fenster echte Löcher), tools/gen_bauteile.gd erzeugt GLB, Kollision, Registry-Fragment
- Alte gebaeude-Sektion wird beim Laden migriert, altes Bauen-Werkzeug entfernt, Resize behält Gruppennamen
- Tests: test_bauen (258 Checks), Playtest läuft Treppe/Tür
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sly 25.10.: „teilweise sehr harte Kanten in den Übergängen“. Ursache: bei
aktiver Symmetrie kopierte jeder Mal-Schritt das GANZE Snapshot-Quadrat um
den Pinsel auf die Gegenseite — dort vorhandene weiche Verläufe wurden an
den Quadratkanten gerade abgeschnitten. Jetzt werden nur Punkte gespiegelt,
die der Schritt verändert hat; überlappen beide Seiten, gewinnt wie bisher
der näher am Strichpunkt liegende Wert.
Test: test_terrain_mirror 109/109 (neuer Fall schlägt mit altem Code fehl).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Sly 25.10.: harte Kanten an Strichrändern, Pinsel auf 1 m, Regler für die
Kantenschärfe.
- apply_material_brush(…, weichheit): im äußeren Anteil des Radius wird
gestreut ausgedünnt (fester Hash je Punkt → deterministisch, Nachmalen
füllt nicht auf); der Shader macht daraus einen ausfransenden Übergang.
Kein Formatwechsel (ein Material je Punkt bleibt).
- Panel: Regler 0–100 % (Default 40 %), nur im Malen-Modus; Pinselgröße ab 1 m.
- test_mal_fein 26/26 (Dichte fällt nach außen, deterministisch, 1-m-Pinsel),
test_terrain_mirror an die Tool-Weichheit angepasst.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sly 25.10.: „immer noch harte Kanten beim Malen“. Ursache: die Splat-Textur
hatte beim feinen 1-m-Raster genau einen Texel je Materialpunkt und wird
nearest gesampelt → jede Zelle ein harter Block.
- Shader liest die Material-ID-Karte (R8, ein Texel je Punkt, texelFetch)
und mischt PRO PIXEL bilinear über die 4 Nachbarpunkte.
- Leichter Domain-Warp (Value-Noise in Rasterzellen, eine Oktave) verwackelt
die Abtaststelle → keine Rastertreppen, natürliche Kante.
- Malen aktualisiert nur noch die Bytes der Karte (kein Splat-Neuberechnen).
- TerrainArrays.material_image (Spiegel-Datei, identisch im Client).
- Render-Probe tests/probe_mal_weich_render.gd (vorher/nachher geprüft).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sly: „feiner malen“. Die Material-Schicht bekommt ein eigenes Raster
(terrain.material_resolution, optional; Default = resolution → alte Karten
unverändert gültig): ~1 m pro Punkt, auf dem Höhenraster ausgerichtet,
max. 1025 (1024 m → 1025, 256 m → 257, 2048 m → 1025 = 2 m).
- SvmapIO: material_resolution / material_resolution_for / resample /
upgrade_material_layer / material_at_height_point; V15 prüft Raster + Länge.
- Neue Karten starten fein; beim Öffnen werden alte Karten optisch identisch
angehoben (Datei ändert sich erst beim Speichern); Resize rechnet die
Schicht auf ihrem eigenen Raster an der Weltposition um.
- Pinsel, Spiegeln (Symmetrie live + „Symmetrie anwenden“) und Live-Splat
arbeiten auf dem Material-Raster; Vertex-Farben lesen den passenden Punkt.
- Malen-Hinweis zeigt die echte feinste Einheit.
- Tests: test_mal_fein 22/22 (Raster, Anheben, 2-m-Pinsel ≈ 13 Punkte,
V15, Roundtrip, Resize); test_terrain/test_terrain_mirror/
test_symmetry_onclick auf das neue Raster nachgezogen; Identitäts-Runner OK.
Kette Editor → Server (Prüfsumme) → Client per run_map_download_e2e 15/15.
Format-Spiegel: Match-Server svmap_loader (V15) + Client svmap_terrain.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- _pick_object: Kugel-Näherung nur noch Vorfilter, entschieden wird über den
echten Mesh-Treffer (SelectionMath.ray_mesh_distance, Faces gecacht).
Ohne Mesh-Treffer bleibt die alte Kugel-Wahl als Fallback.
- Terrain-Panel: im Malen-Modus ist der Stärke-Regler weg (Malen setzt das
Material zellenweise, Stärke hatte nie eine Wirkung); stattdessen steht die
feinste Mal-Einheit der Karte da (size / (resolution - 1)).
- Tests: test_klick_exakt 6/6 (reproduziert Gebäude-Kugel vor Mauer),
test_mal_hinweis 4/4. Vorhandene Suiten unverändert (4 schon vorher rote
Umgebungs-Tests: assets_panel, identitaet_client, model_sources,
playtest_identitaet).
Ersetzt die nie gepushten Arbeitskopie-Änderungen der Karten t_12feca66 /
t_c487a430 / t_271a77bd (Material-IDs per lerp gemischt, Sub-Zellen ohne
Client/Server-Unterstützung).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Gleiche Ursache wie im Client (BEWEISE.md in work/terrain_blending_v2_t_5c94d098_bilder/):
splat_side_for(129) = 512 px = 2 m/Texel auf der 1024-m-Standardmap (8 m/Zelle)
-> Gewichtsrampe in 2-m-Stufen, sichtbar als Baender/Stufen an der Materialgrenze.
Aenderung Editor:
- library/terrain_arrays.gd (Spiegel-Datei, byte-identisch zum Client):
splat_side_for(res, map_size_m = 0.0) = MAX(4 Texel/Zelle, 1 m/Texel aus der
Karten-Kantenlaenge), Zweierpotenz, geklemmt [256, 1024]; build_splat_bytes
ohne Allokation je Texel + Schnellpfad (gleiche ID in allen vier Nachbarzellen)
-> unveraenderte Bytes, deutlich schneller.
- src/viewport/terrain_mesh.gd: _build_splat bekommt size_x/size_z und gibt sie an
splat_side_for; der Live-Sub-Rect-Pfad greift nur noch, wenn auch die
Kantengroesse stimmt (Size-Aenderung ohne res-Aenderung baut die Splat neu).
- tests/test_terrain_splat.gd (Spiegel, identisch im Client): +6 Kartenmass-Checks
(31/31).
Suiten (frischer Forgejo-Klon): test_terrain_splat 31/31, test_terrain 155/155,
test_terrain_mirror 106/106.
Beim Start des Test-Modus versteckt der Editor die Panels — Godot
klappt dabei die Splitter zusammen (Lage springt z. B. [-320,0] →
[160]), beim Wiedereinblenden baut er die Trenner mit Defaults wieder
auf → die fein gezogenen Seitenbereiche waren weg („riesen groß").
Fix in main.gd (besitzt die UI, überlebt den Playtest-Node): beim
Start alle split_offsets von MainSplit/BottomSplit sichern, im
Exit-Handler deferred UND im nächsten Frame zurücksetzen (das Layout
muss die Panels erst neu vermessen haben). Suite test_playtest 52/52
(mit Splitter-Regression: merken → verbiegen → Exit → unverändert),
test_ui_layout 79, test_gui_smoke 92 grün.
Bug: Die Laufrichtung kam aus transform.basis des Körperknotens — die
Kamera-Drehung steckt aber im look_cam-Kindknoten, der Körper rotiert
nicht → WASD lief stur in eine Himmelsrichtung. Auf der Quest rotiert
der ganze Origin mit dem Kopf, dort trat der Bug nicht auf.
Fix: forward/right aus yaw_deg bauen (identisch zu view_direction(),
identisch zum Kopf-Yaw des Clients). Regression in test_playtest:
yaw 0° + W → -Z, yaw 90° + W → -X. Suite 39/39 grün.
Sly-Befund 20.10.: Im Editor-Testmodus hing man an unsichtbaren Wänden weit
VOR den Gebäuden. Ursache: _collect_obstacles nutzte die Viewport-Metas
pick_radius (Kugel-Näherung = volle Raumdiagonale + Zuschlag) — bei der
Kaserne (17×35 m) blockte das ~40×40 m. Die Trimesh-Fixes aus t_9558038d
wirken nur im Spiel-Client, der Editor-Playtest baut seine Blockierung
selbst (keine Physik-Welt, nur AABBs).
Fix: Hindernis-AABB aus der Registry (collision.size × scale, um rot_y_deg
konservativ gedreht, Y vom Fußpunkt bis Fußpunkt + Höhe — gleiche
Konvention wie der Client-Kollider). Fallback: Kugel-Näherung bleibt für
Objekte ohne Registry-Eintrag.
Regression in test_playtest.gd: gedreht/ungedreht, Scale, Fallback
(20 checks, alle grün). Suiten: playtest_comfort 51, playtest_spawn 56,
resize_dialog 44, terrain 153 — ALL OK.
Befund: der Check funktionierte die ganze Zeit — die Rückmeldung kam aber
NUR in der Statuszeile („Du bist auf dem neusten Stand (…).“) und verblasst
dort schnell → las sich wie „tut nichts“. Probe (echter Menü-Klick): Check
läuft, ls-remote ok, Status korrekt.
Fix: nach dem MANUELLEN Check (report=true) öffnet ein kleines AcceptDialog
„Update-Check“ mit Klartext-Ergebnis (Stil wie Map-Check-Dialog):
- up_to_date: „Du bist auf dem neusten Stand (<sha>).“
- update_available: Zusätzlich zum Banner das Fenster mit local → remote
und Hinweis auf „Jetzt updaten“.
- unreachable / auth_failed / error: gleicher Klartext wie Statuszeile.
Der Still-Start-Check (report=false) zeigt bewusst KEIN Fenster (nur Banner
bei „Update verfügbar“) — Probe belegt: Popup sichtbar=false.
Probe: probe_update_check (echter Menü-Klick → Popup sichtbar + Text).
Suiten: test_updater 75 OK, test_resize_dialog 44 OK, test_theme 35 OK,
test_gui_smoke 92 OK, test_ux 137 OK, test_ui_layout 79 OK.
Ursache (Proben im Container, xvfb): autowrap-Labels OHNE Mindestbreite
melden VOR dem ersten Layout eine explosionsartige Mindesthöhe (Wrap bei
~1 px → jedes Wort eine Zeile). popup_centered() übernimmt genau diesen
Wert als Fensterhöhe: Resize-Dialog 5930 px statt ~470, Neue-Map-Dialog
4781 px statt ~390 — beide waren betroffen. Nach dem Layout fällt der
Mindestwert zurück, die Fenstergröße bleibt aber.
Zusatz-Befund: wächst die Live-Vorschau NACH dem Öffnen (Preset 256 →
Warnband), bleibt das Fenster oben hängen → Buttons bei y≈940 in einem
900-px-Sichtbereich = unerreichbar.
Fix:
- resize_map_dialog + new_map_dialog: autowrap-Labels bekommen
Mindestbreite ~ Dialogbreite → Umbruch rechnet mit echter Breite,
Fenster normal hoch (Probe: 481 px).
- resize_map_dialog: Inhalt in ScrollContainer (horizontal aus), Höhe
dynamisch = min(Inhalt, sichtbarer Platz); nach jeder Vorschau-Änderung
wird das Fenster gedeckelt und neu zentriert (_fit_dialog/
_fit_after_layout) → OK/Abbrechen IMMER erreichbar. unresizable.
- tests: test_resize_dialog prüft jetzt Mindesthöhe VOR dem Popup
(absolut, < 1500 px) für beide Dialoge + echten Menü-Klick;
Preset-Button-Suche robust über find_children.
Proben (xvfb): Dialog 662×481, nach Preset 256 → 662×631, unten 765 < 900,
Buttons sichtbar (Beweisbild work/dialog_beweis/resize_dialog_fix.png).
Suiten: test_resize_dialog 44 OK, test_theme 35 OK, test_gui_smoke 92 OK,
test_ux 137 OK, test_ui_layout 79 OK.
popup.id_pressed.connect(main.call.bind("_on_menu_action")) rief effektiv
call(6, "_on_menu_action") → „Cannot convert argument 1 from int to
StringName“ (map-builder.log 20.10., 2 Treffer) — KEIN Menü-Eintrag tat
etwas (Neue Map/Öffnen/Speichern/Kartengröße/Updates). Alle anderen
.call.bind-Stellen hängen an arg-losen Signalen (pressed/timeout) und
waren unbeeinträchtigt. Fix: Lambda mit expliziter Reihenfolge
(Stil wie grid_btn/contours_btn). Regression: test_resize_dialog stellt
jetzt den echten Klick nach (id_pressed.emit(6) am Datei-Popup) — vorher
rief die Suite _open_resize_dialog() direkt auf und sah den Bruch nicht.
Suiten: test_resize_dialog 42 OK, test_theme 35 OK, test_gui_smoke 92 OK, test_ux 137 OK.
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: der Knopf 'Test-Start' fehlte; tests/test_playtest_spawn.gd brach
mit "Nonexistent function '_test_start_reload'" ab und hing.
- Werkzeug-Modus 'teststart' wieder in _set_tool_mode (+ enter/leave/sync)
- ShortcutPolicy.esc_action: exit_teststart inkl. Test
- Taste T: Editor = Werkzeug umschalten, Lauf-Modus = Punkt setzen + Teleport
aufs Fadenkreuz (playtest.teleport_crosshair)
- Entf im Werkzeug loescht den Punkt; Klartext-Hinweis des Werkzeugs wieder in
der Statuszeile ('auf dem Boden unter dem Objekt')
- Leisten-Knopf '🎯 Test-Start' als Umschalter (registry.gd ACTIONS)
- neues Panel-Modul src/ui/panels/test_start_panel.gd + Registry-Eintrag
- Haken _test_start_apply/_delete/_reload + test_start_point/test_start_panel/
test_start_btn stehen fest auf main (Vertrag der Pruefsuite)
- _test_start_reload() wieder nach Neu/Laden/Speichern; playtest.enter() bekommt
den aufgeloesten Startpunkt (TestStart.resolve_start)
Belegt: test_playtest_spawn 56/56, test_shortcuts 204, gui_smoke 92, ui_layout 71,
Import 0 Fehler.
fit_to_document setzt die Ansicht jetzt auf START_DISTANCE_M = 80 m über der
Kartenmitte (XZ-Mitte der Map-Bounds, Höhe = Gelände dort), yaw 45°, pitch 35°.
Die Regel ist unabhängig von Kartengröße (256/1024/4096) und Inhaltslage;
leeres Dokument ebenfalls 80 m. Statuszeile: „Ansicht: Kartenmitte, Abstand
80 m (Pos1 = ganze Karte).“ Pos1/Home bleibt fit_to_bounds = ganze Karte
(MAP-Bounds werden weiterhin gemerkt, nicht die Content-Bounds).
Ziel-Y bewusst auf der Geländehöhe: die y-Mitte der Bounds liegt bei neuen
Maps 35 m über dem Boden — damit hätte die Ansicht über den Boden gezielt und
die Objekte stünden am unteren Bildrand.
Suite test_orbit_camera auf die neue Regel umgestellt (50 Checks):
Startabstand/Richtung, 80-m-Regel über Größen und Inhaltslagen, leeres
Dokument, „Pos1 ≠ Startansicht“ (80 m → 1215,6 m, alle 8 Map-Ecken).
Dazu Renderprobe tests/probe_k1d_render.gd (2 PNGs).