Fehler: _origin_bottom_center backt zuerst die Weltmatrix ins Mesh und wendet
DANN die Zentrierung an - zurueck gibt es aber nur die Zentrierung. Den Stufen
1-3 wurde nur diese aufgedrueckt, ihre Weltmatrix blieb ungebacken. Ergebnis:
sie landeten bei MINUS der Standposition. Gemeldet fuer einen Baum bei x = 18 m,
dessen LOD1-3 bei x = -1894 cm sassen - exakt die Standposition, in Zentimetern
wegen FBX_SCALE_ALL. Jetzt backt jede Stufe erst ihre EIGENE Weltmatrix, dann
kommt die gemeinsame Zentrierung aus LOD0.
Warum der Test das nicht gefunden hat: er baute den Baum am URSPRUNG. Dort ist
minus null eben null. Der Baum steht jetzt bewusst bei (18, -7.5), und je Stufe
wird die X/Y-Mitte im reimportierten FBX gemessen - an derselben Stelle wie die
Radialitaets-Pruefung.
Gegenprobe gemacht: mit zurueckgedrehtem Fix meldet der Test LOD1-3 bei
-17.87/7.46, also exakt minus der Standposition, und faellt mit 6 Fehlern durch.
Mit Fix liegen alle Stufen bei 0.000-0.135 um den Ursprung.
Ausserdem Dichte 170 -> 150. Grund: mit der REALEN Diamond-Card (6 Tris, nicht
die 2-Tri-Attrappe aus meinen Sweeps) landet die Eiche bei Dichte 170 auf 4206
Tris - 2778 Krone plus 1428 Stamm - und damit ueber dem 4000er-Budget aus
ASSETS.md Teil 6. Die abgenommene Krone (2778) und das dokumentierte Budget
(4000) sind also nicht gleichzeitig haltbar. 150 liegt im abgesegneten
Sweetspot 130-170 und haelt das Budget mit 3816. Wenn der Look bei 170 wichtiger
ist, muesste das Budget auf ~4300 - das ist eine Entscheidung der Spielseite.
Nebenbei: test_leaves hatte leaf_size fest auf 0.22 stehen (alter
Blaettchen-Wert) und pruefte gegen ein fest verdrahtetes 2500er-Budget. Beides
kommt jetzt aus den Einstellungen.
23 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Blatt-Defaults auf die Bueschel-Atlas-Aera umgestellt. Eine Card ist ein ganzer
Zweigbueschel, kein einzelnes Blatt - die alten Werte streuten hunderte winziger
Cards, wo ein paar Dutzend grosse hingehoeren. Werte an einer S4-Eiche in UE
gemessen und abgenommen:
Groesse 0.22 -> 0.85
Dichte 500 -> 170
Max Radius 0.02 -> 0.04 <- der Volumen-Hebel
Kippung 0.5 -> 0.9
Max Radius ist der unscheinbare Held: bei 0.02 trugen nur die aeussersten
Zweigspitzen Laub, die Krone hatte nur Kontur und war innen nackt.
Bueschelgroesse haengt jetzt am Wachstum: make_object merkt growth_t am Objekt
(aus dem Namen zu lesen waere bruechig), der Blattwerk-Operator skaliert die
Cards damit. Gemessen ueber vier Stufen: Card-Kante 0.19/0.28/0.37/0.45.
Falle dabei: "Dichte" ist Punkte pro m2 Zweigflaeche, und ein Setzling hat davon
fast keine - bei Stufe 1 kamen exakt NULL Cards heraus, der Setzling stand kahl
da. Da die Bueschel um denselben Faktor kleiner sind, deckt jeder nur faktor^2
der Flaeche; die Dichte gegenlaeufig anzuheben haelt die Blattdeckung gleich.
Tri-Budget PRO STUFE (ASSETS.md Teil 6): S1 <= 800, S2 <= 1200, S3/S4 <= 4000.
Bewusst Stufen und keine Interpolation; die Schwellen greifen auch bei drei
Stufen richtig (t = 0 / 0.5 / 1.0). Gemessen 494 / 878 / 1032 / 1396 - alle im
Rahmen.
WICHTIGSTE FALLE, von der UE-Seite gemeldet und hier nachgestellt:
md[socket] = wert loest KEINE Neuauswertung des GN-Modifiers aus. Die
Blattebene blieb bei 118 Faces stehen, obwohl die Dichte von 90 auf 400
geschrieben war - auch view_layer.update() half nicht, erst ein
show_viewport-Toggle. Der Knopf-Weg funktionierte bisher nur ZUFAELLIG, weil
make_leaves jedes Mal auch die Objekt-Zeiger neu schreibt und das Aendern eines
ID-Zeigers die Depsgraph-Beziehungen umbaut. Wer das spaeter optimiert ("nur
geaenderte Werte schreiben"), haette den Fehler still zurueck.
_modifier_anstossen() macht es jetzt explizit - in make_leaves UND make_object.
Nebenbei: der Variantentest rundete Card-Flaechen auf 5 Stellen; seit die
Kippung bei 0.9 steht streuen sie in der sechsten und eine einzige Card-Sorte
zerfiel in vier Eimer.
Neu: tests/test_blatt_bueschel.py. 23 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Punkt 5 - LOD-Kette fuer den Stand-Baum
Haken "LOD-Kette erzeugen" liefert <Name>_LODGroup.fbx mit _LOD0..3 unter
einem fbx_type=LodGroup-Empty, wie die Fels-Pipeline. NUR der Stand-Baum -
die Faell-Teile leben Sekunden als Physik-Actor, dort reicht LOD0.
Blatt-Reduktion ueber die DICHTE, nicht ueber Decimate: eine Card besteht aus
zwei Dreiecken und laesst sich nicht vereinfachen, man muss weniger davon
setzen. Die verbleibenden werden groesser, damit die Krone nicht ausduennt.
LOD0 578 Rinde / 170 Blatt / 1306 Tris
LOD1 477 / 118 / 912
LOD2 364 / 66 / 575
LOD3 221 / 28 / 302 (16 % der Cards von LOD0)
Kugel-Normalen auf JEDER Stufe gemessen, nicht nur LOD0 - LOD1-3 haben eine
eigene Card-Verteilung. Am reimportierten FBX: 100 % radial auf allen vier.
Zwei Reihenfolge-Sachen: das Rinden-Decimate laeuft VOR dem Verschmelzen,
danach wuerde es die Blatt-Cards mit einschmelzen. Und der Ursprung kommt aus
LOD0 (Unreal platziert danach); alle Stufen werden um denselben Betrag
verschoben, sonst springt der Baum beim LOD-Wechsel.
Punkt 6 - Aufraeumen und Pruefen
Blatt-Streuung ueber alle neun Presets nachgemessen. Der Max-Radius-Filter
arbeitet richtig: 0-22 % der Cards landen am Stamm (meist unter 9 %), und
alle Presets bleiben unter dem 2500-Tri-Budget (Maximum weide 2246).
Messfalle, die ich dabei selbst produziert habe: zuerst hatte ich den Anteil
der VERTICES unter der Radius-Schwelle gemessen - beim Busch 95 %, sah nach
einem Problem aus. Das misst aber die Vertex-Verteilung, nicht wo Blaetter
landen; ein Busch besteht fast nur aus duennen Zweigen. Die richtige Frage
beantwortet erst ast_id.
Der Kaktus erzeugt regulaer 0 Cards (kein Ast duenner als Max Radius, botanisch
richtig). Das lief still durch und man stand vor einer leeren _Leaf-Ebene -
jetzt sagt der Operator es und nennt den Regler beim Namen.
Panels: das Export-Panel trug Stand-Baum, LODs und Faell-Teile in einem
Kasten. Die Teile haben jetzt "5 - Faell-Teile" fuer sich.
Neu: tests/test_baum_lod.py. 22 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Knopf "Faell-Teile exportieren": Stamm in 2-3 Segmente, jeder Ast als eigenes
Teil SAMT seinen Sub-Aesten und Blatt-Cards, dazu <Name>_Pieces_All.fbx mit
gemeinsamem Ursprung. Je Teil zwei Material-Slots, Pivot am Massenmittelpunkt,
eigene UCX-Huelle.
Die Ast-Zugehoerigkeit ist ein ATTRIBUT, keine Schaetzung - wie von der
UE-Seite vorgeschlagen. Der Generator legt "ast_id" an den Astpunkten ab
(0 = Stamm, 1..N = Aeste). Sub-Aeste erben die ID ihres Astes automatisch, weil
sie aus Punkten auf dessen Kurve entstehen; nachgemessen: bei Sub Count 0 hat
jeder Ast 28 Verts, bei 3 dann 64, der Stamm bleibt konstant bei 156. Beim
Blatt-Streuen wird ast_id am Punkt abgegriffen und auf die Card gespeichert.
Damit ist die Zuordnung exakt nachzaehlbar, und genau das prueft der Test:
Summe der Cards ueber alle Teile == Cards des Stand-Baums. 170 = 170.
Die Falle, die diese Bilanz aufgedeckt hat: die Z-Schnitte am Stamm zerschnitten
anfangs auch Blatt-Cards. Aus einer wurden zwei, die Bilanz stand auf 178 gegen
170 - und die Atlas-UVs der Haelften waeren kaputt gewesen. Eine Card ist ein
unteilbares Billboard; sie wandert jetzt als GANZES in das Segment, in dem ihre
Inselmitte liegt. Nur die Rinde wird geschnitten, ihre Schlagflaechen werden
gedeckelt.
Massenmittelpunkt flaechengewichtet statt Vertex-Mittelwert: wo das Mesh dicht
tesselliert ist (Astansatz), wuerde der Mittelwert den Pivot dorthin ziehen und
das Teil torkelte beim Fallen.
Alles am REIMPORTIERTEN FBX gemessen: 12 Teile (3 Stamm, 9 Ast), UCX 18-32
Verts im Budget, Pivot maximal 16 mm vom Schwerpunkt, 0 non-manifold Kanten,
Gesamtdatei 5.41 m hoch - also volle Baumhoehe und deckungsgleich
rekonstruierbar.
Nebenbei: _deselect fiel ueber None-Eintraege in der Objektliste, die beim
Entfernen von Objekten zwischen den Teilen staendig auftreten.
Neu: tests/test_faell_teile.py. 21 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Leaf Card" kann jetzt eine COLLECTION sein: je Blattpunkt wird zufaellig eine
Card gezogen (Pick Instance, seed-stabil). Damit legt der Nutzer 4-6
Diamond-Cards mit unterschiedlichen Atlas-UVs an und die Krone mischt sie.
Rueckwaertskompatibel - ein einzelnes Objekt geht weiter, die Sammlung hat
Vorrang.
Umsetzung: Collection Info mit Separate Children (sonst kaeme die Sammlung als
ein Klumpen an und Pick Instance haette nichts zum Auswaehlen), Geometry-Switch
zwischen Einzel- und Sammlungspfad, Instanz-Index aus einem Random Value, dessen
Obergrenze aus der TATSAECHLICHEN Anzahl kommt - eine feste Obergrenze wuerde
bei weniger Cards Luecken erzeugen. Geometry Nodes kann einen leeren
Collection-Zeiger nicht selbst pruefen, deshalb setzt make_leaves einen
"Use Collection"-Schalter.
Beweisfuehrung im Test: die Zufalls-Groesse wird ABGESCHALTET, dann kommt jede
Groessenabweichung nur von der Card-Variante und die Anzahl verschiedener
Face-Flaechen ist ein exakter Zaehler. Gemessen: Einzel-Card 2 Flaechen (ein
Card-Paar), Sammlung mit 4 Cards 10 Flaechen - alle Varianten kommen vor.
Gleicher Seed identisch, anderer Seed anders.
Nebenbei: die Vorpruefung des Blattwerk-Operators war eine zusammengesetzte
Bedingung, die bei gesetzter Sammlung ohne Einzel-Card auf None zugriff.
Neu: tests/test_blatt_varianten.py. 20 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Punkt 1 - "Spielfertig exportieren"
Stamm und _Leaf-Ebene werden zu EINEM Mesh mit zwei Material-Slots
verschmolzen (Slot 0 = M_Bark, Slot 1 = M_Foliage). Die getrennten Objekte
im Blend bleiben die Arbeitsebene - verschmolzen wird nur eine Export-Kopie.
Ursprung Bbox-Unterseite Mitte und FBX-Einstellungen wie in der
Fels-Pipeline (use_selection, FBX_SCALE_ALL).
Punkt 2 - Kugel-Normalen auf die Blatt-Cards
Alle Blatt-Normalen zeigen radial vom Kronen-Schwerpunkt nach aussen, damit
die Krone als EIN weiches Volumen liest statt als Haufen flacher Bleche.
Der Schwerpunkt wird nur aus den BLATT-Faces gebildet - der Stamm wuerde die
Mitte nach unten ziehen und die Kugel verkippen.
Bewusst direkt ueber normals_split_custom_set statt ueber den
Normal-Edit-Modifier: der Modifier muesste vor dem Export angewendet werden,
und angewendete Modifier plus Custom Normals ist genau die Kombination, bei
der der FBX-Export gerne flachbuegelt. Am REIMPORTIERTEN FBX gemessen:
mittlere Radialitaet 1.000, 100 % der 510 Blatt-Loops ueber 0.8. Gegenprobe
Rinde 0.043 - die bleibt unangetastet.
Drei Fallen unterwegs:
- Ueber bmesh.from_mesh zusammenfuegen haengt die Geometrie zwar an, wirft
aber die MATERIAL-INDIZES weg: danach lagen alle 748 Faces auf Index 0,
Blaetter und Rinde ununterscheidbar. Blenders join fuehrt die Slot-Listen
zusammen und rechnet die Indizes korrekt um.
- join loescht das zweite Objekt; jede gehaltene Referenz darauf ist danach
tot. Die Aufraeumliste fuehrt deshalb NAMEN statt Objekte.
- Die Export-Kopie muss exakt so heissen wie der Baum (Unreal leitet den
Asset-Namen davon ab). Solange das Original den Namen haelt, haengt Blender
".001" an - Original wird fuer die Exportdauer beiseite benannt, wie bei den
Felsen.
Neu: tests/test_baum_export.py misst die Normalen im EXPORTIERTEN FBX, nicht
am Szenenobjekt - nur so faellt ein plattgebuegelter Custom-Normal auf.
19 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Weniger einstellen muessen: oben im Panel Sorte waehlen, Haken druecken,
erzeugen. Die neun Unter-Panels bleiben fuer den Feinschliff.
Findling rund, weiche Kanten ~1160 V / ~2320 Tris
Platte flach, liegt satt auf ~670 V / ~1340 Tris
Kantig Bevel + Weighted Normal ~350 V / ~690 Tris
Erzbrocken dicht genug fuer die Maske ~1280 V / ~2560 Tris, Slot M_Rock_Ore
dissolve_angle ist jetzt ein BEREICH statt eines festen Werts - direkt aus der
Rueckmeldung "hab ich immer auf 17-25 je nach Stein". Je Stein wird einer
gezogen; gemessen an sechs Findlingen bei 17-25 Grad: 1936-2928 Tris. Beide
Regler gleich setzen = fester Winkel wie frueher.
Zwei Sachen fielen beim Messen auf:
1) Der Dissolve-Winkel frisst deutlich Geometrie. Derselbe dichte Satz liefert
bei niedrigem Standardwinkel ~2700 Tris, bei 17-25 Grad nur noch ~1030 und
faellt damit unter ORE_MIN_VERTS - das Erz-Set haette seine EIGENE Warnung
ausgeloest, sobald vorher eine Sorte mit hohem Winkel gewaehlt war. Der
Bereich gehoert deshalb mit in DENSE_PRESET. Ueber vier Seeds und beide
Groessenklassen nachgemessen: 0.70/0.92 haelt den Worst Case bei 847 Verts.
2) Erz-Dichte und Tri-Fenster kollidieren. Die Maske braucht ~800 Verts auch
beim kleinen S-Brocken, das treibt einen M-Stein auf ~3400 Tris - ueber das
Fenster 1500-3000. Deshalb entkoppelt: normale Sorten leichter (0.40/0.80),
Erzbrocken knapper (0.45/0.82), das S+M-SET dichter (0.70/0.92), weil dort
die kleinen Brocken die Untergrenze setzen.
Nebenbei gefunden und behoben: der erzwungene Ursprung wurde aus der
GEMEINSAMEN Bbox aller LOD-Stufen berechnet. Eine stark dezimierte Stufe kann
tiefer reichen als LOD0 - dann sass LOD0 sieben Zentimeter ueber dem Pivot und
der Stein haette in UE geschwebt. Unreal platziert nach LOD0, also gibt LOD0
jetzt den Ursprung vor.
Neu: tests/test_schnellstart.py. 18 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drei Rueckmeldungen von der Spielseite abgearbeitet.
1) Fehlende Material-Slots
"Material zuweisen" stand auf Standard AUS, deshalb kamen alle Proben mit
mats: [] an - Unreal legt dann nur einen Default-Slot an und die
eingefrorene Konvention M_Rock_<Sorte> greift nicht. Jetzt Standard AN.
Normale Steine rotieren durch M_Rock_Granite/Basalt/Sandstone/Mossy, das
Erzfels-Set bekommt einen NEUTRALEN Slot M_Rock_Ore - ein Set fuer alle
Erzsorten, die Sorte macht die Material-Instanz.
2) Namens-Drift Fels_ statt Rock_
War ein Versehen, keine Absicht: der Standard-Praefix stammte noch aus der
ersten Fassung, bevor die Konvention Rock_<Set>_<Groesse>_<NN> stand. Der
Erz-Set-Knopf setzte "Rock" schon selbst, der allgemeine Standard nicht.
Standard ist jetzt "Rock" - keine Asset-Zwillinge, keine Migration noetig.
3) Erzfelsen mit ~870 statt ~2700 Tris
Nachgestellt: aus den Werkseinstellungen liefert der Set-Knopf 2590-2690
Tris, die Automatik greift also. ~870 entstehen genau dann, wenn man den
Erz-Haken setzt und den NORMALEN Erzeugen-Knopf nimmt - Werkseinstellung ist
Decimate 0.20/0.50 statt 0.40/0.80. Das ist eine Bedienluecke, kein Fehler
in der Automatik: die Dichte wird bewusst nur ueber den Set-Knopf gesetzt,
damit niemandem seine Einstellungen unterm Hintern weggezogen werden.
Deshalb warnt der Operator jetzt, wenn mit Erz-Haken ein Mesh unter 800
Verts entsteht, und nennt den Knopf "Smooth & dicht" beim Namen. Sonst
faellt das erst in UE auf und sieht dort nach einem Material-Fehler aus.
Tests halten den Vertrag jetzt fest: Auslieferungszustand (assign_material an,
Praefix Rock), neutraler Erz-Slot, Sorten-Slots bei normalen Steinen.
17 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Entscheidung der Spielseite: die A-Semantik bleibt eingefroren (1.0 = kein Erz),
der Master loest es ueber
Erz = (1 - A) * ErzMenge
mit ErzMenge als MI-Regler, Standard 0. Normale Stein-MIs ignorieren den Kanal
damit vollstaendig, nur die Erz-MIs drehen ihn auf. Kein Re-Export des
25er-Sets noetig, keine Fallunterscheidung im Addon.
Dafuer musste die Maske umgedreht werden: bisher lag das Nugget-ZENTRUM auf
A = 1.0 - also auf demselben Wert wie "kein Erz". Unter der Master-Formel waere
das Erz damit unsichtbar gewesen und der ganze RESTLICHE Stein haette als Erz
gegolten. Jetzt laufen die Nuggets auf A = 0 zu.
Gemessen: ohne Erz A 1.00..1.00 und (1-A) max 0.000 - ein normaler Stein zeigt
im Master garantiert nichts. Mit Erz erreicht (1-A) 1.00, Abdeckung unveraendert
4/9/15 % bei Anteil 0.08/0.18/0.35. Die Erzstellen sitzen an denselben Punkten
wie vorher, nur andersherum kodiert.
Die Tests pruefen jetzt direkt gegen die Master-Formel statt gegen den Rohwert -
am Rohkanal ist die Richtung zu leicht zu verwechseln. Panel zeigt bei aktivem
Erz-Bake "A=Erz (0=Erz)" plus den Hinweis auf die Formel, damit das niemand
versehentlich zurueckdreht.
Nugget-Groesse bleibt wie entschieden ABSOLUT (Radius in Metern).
ACHTUNG: Erz-Assets, die mit 2.15/2.16 exportiert wurden, tragen die alte
Richtung und muessen neu exportiert werden. Steine ohne Erz-Bake sind nicht
betroffen - deren A war und ist durchgehend 1.0.
17 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kein Laufzeit-Fracturing im Spiel - die Teile werden beim Anschlagen als
Physik-Actors gespawnt, entstehen hier also als vollwertige Assets.
Knopf "In Bruchstuecke zerlegen" arbeitet auf der Auswahl: <Name>_Piece_01..NN,
2-4 Teile, Bruch-Seed, optional Mutterstein ausblenden. Jedes Teil bekommt
Vertex-Layout v2, Ursprung unten-mittig, die gleichen Material-Slots wie der
Mutterstein und beim Export eine eigene UCX-Huelle.
Punkt 4a: "Ein Stueck mit Erz" benennt ein zufaelliges Teil <Name>_Piece_Erz
und schreibt Erz-Adern in dessen A-Kanal. Alle anderen Teile behalten A = 1.0.
Ob es gespawnt wird, entscheidet die Loot-Logik im Spiel.
Zusammenpassende Bruchkanten ohne Abstimmung: Voronoi ueber Halbraeume. Fuer
jedes andere Zentrum wird an der Mittelsenkrechten geschnitten und die
abgewandte Seite verworfen - benachbarte Zellen schneiden dadurch an GENAU
derselben Ebene. Bewusst ohne das Cell-Fracture-Addon (muesste aktiviert sein,
haengt an Operator-Kontext); bmesh laeuft immer.
Die Falle, die mich mehrere Anlaeufe gekostet hat: jeder Schnitt muss SOFORT
geschlossen werden, und zwar ueber genau die Kanten aus geom_cut. Der
naheliegende Weg - am Ende alle Randkanten suchen und fuellen - erzeugte Kanten
mit vier Flaechen (hauchduenne Splitter, 0.000007 m2 gegen Median 0.0025);
raeumte man die ab, blieben Loecher. dissolve_degenerate half nicht, weil es
nach KantenLAENGE geht und ein langer duenner Splitter keine kurze Kante hat.
Aufgefallen ist das Ganze am Volumen, nicht am Auge: die Teile kamen zusammen
auf 72.7 % des Muttersteins, weil calc_volume bei offenen Meshes Unsinn
liefert. Jetzt 100.2 % bei 2, 3 und 4 Teilen, 0 offene und 0 non-manifold
Kanten. Genau dieser Vergleich steht im Test - er ist zugleich das Warnsignal
fuer kaputte Topologie.
Neu: tests/test_bruchstuecke.py. 17 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Punkt 5 - EIN neutrales Erzfels-Set fuer ALLE Erzsorten
A-Kanal traegt die Erz-Maske: sie sagt nur WO Erz sitzt. WELCHES Erz
(Kupfer/Eisen/Gold = Tint + Gluehen) macht die Material-Instanz in UE.
Knopf "Erzfels-Set erzeugen (S + M)" liefert Rock_Ore_S_NN und Rock_Ore_M_NN
und setzt alle Einstellungen danach zurueck. Regler: Erz-Anteil,
Nugget-Groesse (Radius in m), Erz-Seed.
Nuggets statt Noise: gestreute Zentren mit weichem Abfall. Ein Noise-Feld
gaebe wolkige Schlieren, Erz sitzt aber in abgegrenzten Einschluessen. Die
Zentren bevorzugen Fugen - Adern folgen Rissen, und das Gluehen liest sich
in einer Vertiefung besser.
Ohne Erz-Bake bleibt A auf 1.0 - wie im eingefrorenen Layout v2 und wie das
bestehende 25er-Set es traegt.
Punkt 6 vorgezogen, weil Punkt 5 davon abhaengt
Die Maskenaufloesung haengt an der Vertex-Dichte. Bei den bisherigen
~150-Vert-Steinen war jedes dritte Vertex ein Nugget-Zentrum: im Render
liefen die Nuggets zu grossen Schlieren zusammen statt einzelne Einschluesse
zu bleiben. Bei ~1350 Verts liest es sich richtig. Deshalb gibt es jetzt
"Smooth & dicht" (Unterteilung 5, Decimate 0.40/0.80, Auto-Smooth) und das
Erz-Set stellt selbst darauf um. Gemessen ueber drei Seeds: 2708/2658/2716
Tris - im Zielfenster 1500-3000. Zusaetzlich ist die Nuggetzahl auf ein
Zentrum je ~10 Verts gedeckelt.
Nebenbei: _asset_name schrieb die Form-Bezeichnung pauschal gross, daraus wurde
"Rock_ORE_S_01" statt des vereinbarten "Rock_Ore_S_01". Einzelne Buchstaben
(Form A, B, C) werden weiter gross geschrieben, laengere Bezeichner behalten
ihre Schreibweise.
Gemessen: Erz-Anteil 0.08 -> 4 %, 0.18 -> 9 %, 0.35 -> 15 % der Oberflaeche,
A 0.00..1.00, S bis 0.80 m / M ab 1.71 m, Maske ueberlebt den FBX-Roundtrip.
Neu: tests/test_erz.py. 16 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Punkt 1 - Ursprung erzwingen (keine Option mehr)
Der Export schrieb die Objekt-Transformation mit ins FBX. Dadurch trug das
25er-Set seine Szenen-Rasterpositionen (bis 20 m) im Asset und die Steine
schwebten im Spiel neben ihrem Platzierungspunkt. Jetzt wandert jedes Objekt
an den Welt-Ursprung, Origin unten-mittig der Bbox. Am REIMPORTIERTEN FBX
gemessen: Bbox-Mitte 0.000/0.000, min.Z 0.000.
Punkt 2 - flache Steine hinlegen
Kuerzeste Bbox-Achse zeigt nach oben. Gedreht wird nur, wenn es etwas bringt:
bei 2.53/1.40/1.54 wuerde Kippen die Form nur willkuerlich drehen.
Punkt 3 - UCX-Kollisionshuellen
Konvexe Huelle als UCX_<Name> im selben FBX, Budget einstellbar (Default 32
Verts). Gemessen 29-32 Verts, geschlossen, umschliesst den Stein.
Alles auf temporaeren KOPIEN - die Szene des Users bleibt unveraendert
(Test prueft Namen und Objektzahl vorher/nachher).
Offener Punkt der Spielseite beantwortet: LOD-Gruppen + Vertex-Farben gingen
NICHT. Der LOD-Zweig in make_one_rock sprang komplett am Cavity-Bake vorbei -
gemessen: kein Farbattribut auf einer der vier Stufen, dazu fehlten Origin und
Material. Jetzt wird auf LOD0 gebacken, BEVOR dupliziert wird; das Decimate
interpoliert mit, damit alle Stufen dieselbe Cavity zeigen und der LOD-Wechsel
nicht poppt. Nach Roundtrip: LOD0 40 V R 0.30..1.00, LOD1 21 V, LOD2 11 V,
LOD3 6 V - alle mit Farben.
Vier Fallen, die der Test aufgedeckt hat:
- Blender haengt an Kopien ".001" an; Unreal ordnet UCX ueber den EXAKTEN
Mesh-Namen zu -> Original bekommt fuer die Exportdauer einen Hilfsnamen.
- bmesh.from_mesh bringt die Originalflaechen mit, convex_hull legt seine
Huelle obendrauf -> 16-21 offene Kanten. Nur die PUNKTE einlesen.
- Jede Vereinfachung schneidet Ecken ab, die Huelle wird KLEINER als der Stein
(der Spieler wuerde einsinken). Jetzt exakt so weit aufgeblasen, dass wieder
alles hineinpasst - fuer Kollision ist minimal zu gross unkritisch.
- ob.matrix_world = Identity loescht die Transformation NICHT bei geparenteten
Objekten; bei einer LOD-Gruppe auf (13/-7.5/2.2) landete das Mesh bei MINUS
dieser Position. matrix_basis und matrix_parent_inverse direkt nullen.
Neu: tests/test_export_robust.py misst am reimportierten FBX statt an der
Szene - nur so faellt eine mitgeschriebene Transformation ueberhaupt auf.
15 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drei gemeldete Probleme - alle bestaetigt, Ursachen gemessen statt geraten.
1) Palme sah "extrem komisch" aus
Gemessen: Kronenradius/Hoehe 0.11 - die Wedel klebten als Mop-Kopf am
Stamm. Zielwert aus zwei unabhaengigen Quellen, die sich decken:
tree-gen palm.py (Weber&Penn) setzt Wedellaenge = 0.3 x Stamm, die
TreeIt-Referenzpalme kommt auf 3.3 m bei 4.33 m = 0.38.
Jetzt 0.30. Dazu der Winkel: tree-gen gibt down_angle 60 + curve 40 an,
also schraeg raus und ueberbiegen. Mit Branch Up 0 allein standen die
Wedel als gerade Antennenspeichen ab; mit Up 0.35 / Droop 1.2 steigen sie
gemessen 1.1 m ueber den Ansatz und kippen dann ab.
2) Busch sah aus wie ein Baum
Gemessen: 1.81 m hoch bei 0.84 m Breite (B/H 0.46), durchgehender Stamm,
Aeste erst ab 15 %, Wurzelanlauf wie beim Baum. Das IST ein Baeumchen.
Botanisch ist der Unterschied nicht die Groesse, sondern die Architektur:
mehrere Stiele ab Boden, kein Leittrieb, gemessen nach Hoehe UND Breite.
Jetzt kurzer Stumpf + Aeste ab Boden: 1.20 m hoch, 2.0 m breit (B/H 1.6).
3) Kleinste Wachstumsstufe sah komisch aus / "nur runterskaliert"
Gemessen: der Setzling war relativ zu seiner Hoehe 2.4x DICKER als der
Altbaum (r/H 0.025 gegen 0.0104), weil Trunk Radius mit 0.30 skalierte,
die Hoehe aber mit 0.16. Botanisch gilt die elastische Aehnlichkeit nach
McMahon: Hoehe ~ Durchmesser^(2/3), also Faktor 0.16^1.5 = 0.064.
Dazu behielt jeder Setzling den vollen Wurzelanlauf -> Trichterfuss.
Neu ausserdem: Aeste schrumpfen mit der Hoehe statt langsamer, sitzen
tiefer (Aufasten kommt erst mit dem Alter) und haengen weniger.
Nicht jede Pflanze waechst wie ein Laubbaum -> GROWTH_OVERRIDES:
Palme kein Dickenwachstum, erst Durchmesser dann Hoehe (UF/IFAS ENH1212:
"its diameter is fixed for life") -> Setzling ist ein kurzer Stumpf
Kaktus Sukkulent, gleiche Logik, und OHNE Arme: Saguaros bilden die erst
mit 50-70 Jahren bei rund 2 m Hoehe (NPS)
Weide haengt erst mit langen Zweigen; sonst reichten sie 0.27 m unter
den Stammfuss und das Asset waere in UE geschwebt
Zwei Bugs fielen dabei auf:
- Branch Count = 0 schaltete die Aeste NICHT ab - dieselbe Falle wie damals
bei Sub Count (Curve to Points klemmt Count auf min. 1). Fuer die Sub-Ebene
gab es laengst einen Geometry-Switch, fuer die Hauptaeste nicht.
- Im Busch-Preset stand "Branch Count" zweimal im selben Dict; der zweite
Eintrag (10) hat den ersten (14) still ueberschrieben.
Die sieben uebrigen Presets sind unveraendert (gleiche Maße, gleiche Tris).
Neu: tests/test_proportionen.py nagelt die Zielwerte fest. 14 Tests bestehen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Panel-Umbau
-----------
Jedes Addon hatte seine Einstellungen als Kette dauerhaft offener Boxen - beim
Rock Generator 15 Stueck, man musste am ganzen Panel vorbeiscrollen, um an den
Erzeugen-Knopf zu kommen. Jetzt steht oben nur, was man bei jedem Asset anfasst
(Vorlage, Name, grosser Erzeugen-Knopf, Zufall), der Rest liegt in nummerierten,
eingeklappten Unter-Panels in der Reihenfolge des Arbeitsablaufs:
Rock Gen 9 Unter-Panels (Benennung ... Textur-Bake)
Tree Gen 4 (Wachstum, Blattwerk, Zusatz-Ebenen, Export)
Grass Gen 4 (Form, Haltung, Batch, Kanal-Belegung)
Asset Utils 5 (war schon so, dient als Vorlage)
Schalter, die ein ganzes Unter-Panel steuern (Vertex-Farben backen,
Wachstums-Stufen), sitzen jetzt als Haekchen im Panel-Kopf - Zustand sichtbar
ohne aufzuklappen.
Gemessen statt geglaubt: test_panels.py registriert alle vier Addons, laesst
jedes Panel zeichnen, prueft Tab-Zugehoerigkeit und Auf-/Zugeklappt-Absicht,
dass jeder angebotene Operator existiert - und vor allem, dass JEDE definierte
Property irgendwo erreichbar bleibt (Zweige wie base_shape='BOX' werden dabei
durchgeschaltet). Ergebnis: 0 von 122 Properties beim Umbau verloren.
Test-Suite
----------
Beim Gegenpruefen kam heraus, dass fuenf Tests still veraltet waren und eine
echte Regression nicht gemeldet haetten:
test_rock_gen suchte Objekte mit Praefix "Rock_" - die heissen seit dem
Benennungssystem "Fels_A_M_01". Fand 0 von 3.
test_base_shapes loeschte am Schleifenanfang ALLE Objekte, auch die eigenen
Kopien aus vorigen Durchlaeufen -> StructRNA removed.
test_ecogame_tree erwartete eine feste Namensliste ohne die spaeter
dazugekommenen _Leaf-Objekte.
test_tree_addon dito, ausserdem Zip-Version 1.22.0 fest verdrahtet.
test_leaves Zip-Version 1.23.0 fest verdrahtet.
Drei Muster vermieden statt nur die Symptome geflickt: neue Objekte ueber die
Differenz vorher/nachher finden statt ueber ein Namens-Praefix, Zips per glob
statt mit fester Version, Zusatz-Ebenen beim Zaehlen herausfiltern.
Neu: run_tests.ps1 faehrt die komplette Suite mit einem Befehl (-Filter fuer
einzelne). Genau das fehlte - deshalb war das Veralten nicht aufgefallen.
13 von 13 Tests bestehen.
Nebenbei
--------
build.ps1 ruft server-generate jetzt mit --html auf (browsbare index.html fuer
statisches Hosting). Nachgemessen und im README festgehalten: zwei Zips
derselben Extension-ID sind KEIN Versions-Archiv - server-generate warnt
("archive found with duplicates ... conflicting blender versions"), mehrere
Versionen einer ID gehen nur bei verschiedenen Blender-Bereichen.
Versionen: Rock 2.13.0, Tree 1.24.0, Grass 1.1.0, Asset Utils 1.1.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fuer handgebaute Assets, die nicht durch einen Generator laufen (Pilze,
Baumstumpf, Erzfels, Props). Panel in fuenf einklappbaren Unter-Panels statt
einer langen Liste:
1 Vorbereiten - Rotation/Scale anwenden, Ursprung unten-mittig, Normalen
2 Vertex-Farben - R=Cavity / G=Teil-oder-Moos-Maske / B=Hoehe / A=1
3 Benennung - <Basis>_<NN>, Mesh-Daten ziehen mit
4 Pruefen - Asset-Check auf alles, was in UE Aerger macht
5 Export - FBX je Mesh mit colors_type='SRGB'
Zwei Bugs beim Bauen gefunden und gefixt:
- v.link_faces gibt es nur in bmesh, nicht auf Mesh.vertices -> lose Vertices
ueber die tatsaechlich benutzten Indizes zaehlen.
- bpy.ops.paint.vertex_color_set braucht den Vertex-Paint-Modus und scheitert im
Object-Mode mit "poll() failed". Zusammen mit vertex_color_dirt im selben try
wurde dadurch der GANZE Bake stillschweigend uebersprungen (R/G/B blieben 1.0).
Initialisierung laeuft jetzt direkt ueber Python.
Verifiziert am nachgebauten Pilz: Check meldet vorher Scale/Rotation/Ursprung,
nach "Vorbereiten" Scale 1/1/1, Rot 0/0/0, min_z 0.0000 und "ok". Teil-Maske
G=1 im Hut, 0 im Stiel. Nach FBX-Roundtrip G-Werte [0.0, 1.0] erhalten.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der User baut Pilze von Hand (Hut und Stiel als getrennte Objekte, mit Strg+J
verbunden) und fragte, was er wegen Vertex-Farben beachten muss.
Zwei Ergaenzungen:
1) Button "Vertex-Cavity auf Auswahl": backt R=Cavity / G / B=Hoehe auf JEDES
markierte Mesh - nicht mehr nur auf generierte Steine. Damit lassen sich
handgebaute Assets (Pilze, Baumstumpf, Erzfels) auf dasselbe Layout bringen.
2) Option "G = Teil-Maske (Shells)": statt der Moos-Maske werden getrennte
Mesh-Teile markiert. Der oberste/breiteste Teil bekommt G=1, der Rest 0.
Bei Strg+J-verbundenen Assets ist die Trennung dadurch EXAKT statt ueber
Normalen geschaetzt - beim Pilz also Hut vs Stiel. Ein Material toent beide
unterschiedlich, ein zweiter Material-Slot entfaellt.
Einteilige Meshes bleiben bewusst unmarkiert (G=0), damit die Maske nichts
Falsches behauptet.
Am nachgebauten Pilz geprueft: 2 Shells erkannt, G im obersten Teil ausschliess-
lich 1.0, im Rest ausschliesslich 0.0, Maske binaer, Einzelteil-Mesh -> nur 0.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gras-, Blumen- und Farn-Bueschel. Kern ist die WIND-UV nach Vorgabe des Users:
UV0.V = 0 an der Wurzel, 1 an der Spitze - genau die Maske, die UEs
"SimpleGrassWind" als WindWeight braucht.
Am Zielmaterial geprueft (M_Grass.uasset): es nutzt SimpleGrassWind +
WorldPositionOffset + TextureCoordinate, aber KEINE VertexColor. Die Windmaske
kommt deshalb primaer ueber die UV - die Assets laufen damit ohne
Materialaenderung.
Vertex-Farben zusaetzlich, nicht als einzige Quelle:
R = Kopie der Windmaske (Fallback)
G = Variation je HALM - Ebene unter dem Biom-Tint, den die Engine bereits ueber
PerInstanceCustomData 0/1 pro Instanz setzt; im Material multiplizieren
B = AO (unten dunkel)
Presets gras / gras_hoch / steppengras / farn / blume, Zufalls-Button,
Tri-Budget-Warnung (400 laut ASSETS.md).
Gemessen: UV.V Wurzel 0.000 -> Spitze 1.000, 112 Tris bei 14 Halmen, G hat
exakt 14 verschiedene Werte bei 14 Halmen (also wirklich pro Halm, nicht pro
Vertex), min_z = 0.0000, UV und VCol ueberleben den FBX-Roundtrip.
build.ps1 baut jetzt drei Extensions - dieselbe URL liefert alle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auf Wunsch des Users neben Wuerfel und Ico-Sphere eine Quader-Grundform mit
frei einstellbaren Proportionen (X/Y/Z) plus Streuung je Stein. Damit entstehen
flache Platten und langgestreckte Riegel - passt besonders zum Plattenlook der
Genshin-Referenz, wo Felsen aus flachen Schichten bestehen.
Wichtiges Detail: die Proportionen werden auf die MESH-DATEN angewendet
(transform_apply), nicht auf das Objekt. Der Displace arbeitet mit globalen
Texturkoordinaten - eine Objekt-Skalierung wuerde das Rauschen mitverzerren
statt die Form zu aendern.
Gemessen bei Proportion (2.0, 1.5, 0.35): 3.43 / 2.56 / 0.79 m (Z = 23 % von X),
bei (2.2, 0.8, 0.9): 3.81 / 1.40 / 1.60 m. Der Zufalls-Button kennt die neue
Form ebenfalls. Neuer Test tests/test_base_shapes.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nach Wunschliste des Users. Attribut "Cavity" traegt jetzt drei UNABHAENGIGE
Masken statt einer eingebackenen Mischung:
R = Cavity pur 1 = offene Flaeche, 0 = tiefe Fuge - OHNE Hoehen-Verlauf
G = Moos/Schmutz aus Oberseiten-Anteil + Fugennaehe + Noise, mit Reglern
fuer Menge, Aufbruch und Seed
B = Hoehen-Verlauf 0 = Unterseite, 1 = oben, separat
A = 1 (Reserve)
Der Punkt: Boden-Saum und Moosfarbe entscheidet jetzt das MATERIAL pro Instanz,
nicht mehr das Mesh. In v1 steckte der Verlauf fest in R und liess sich nicht
mehr trennen.
Gemessen: R ohne Hoehenabhaengigkeit (unten 0.62 / oben 0.59 - vorher war das
der eingebackene Verlauf), B steigt 0.08 -> 0.92, G oben 0.57 vs unten 0.20,
A konstant 1.00. Moos-Seed aendert die Verteilung. Alle drei Kanaele ueberleben
den FBX-Roundtrip.
tests/test_vertex_cavity.py prueft jeden Kanal einzeln inkl. Roundtrip.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Idee kam vom User: die Steine ueber "smart materials" in UE loesen statt ueber
gebackene Texturen. Prozedurales Noise/Voronoi im Shader waere zu teuer (laeuft
pro Pixel), aber der Kern der Idee traegt - deshalb dieser Weg:
Neue Option "Cavity in Vertex-Farben": schreibt Cavity/AO plus optionalen
Hoehen-Verlauf in das Farb-Attribut "Cavity" (vertex_color_dirt + eigene
Nachbearbeitung fuer Staerke und Verlauf). Das Material braucht damit KEINE
Textur: Base Color = Farbton x VertexColor, dazu Kantenlicht und Schattentint.
Gegenueber Triplanar sitzen die dunklen Fugen dadurch auf den ECHTEN Kanten des
Meshes statt darueberzuschweben - genau das passiert in der Genshin-Referenz.
Verifiziert: Attribut wird angelegt, Wertebereich 0.250..0.947 (echter Kontrast),
und es ueberlebt den FBX-Roundtrip UNVERAENDERT (gleicher Bereich nach Import).
Regler: Cavity-Staerke, Hoehen-Verlauf, Weichzeichnen.
Neuer Test tests/test_vertex_cavity.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) KLASSEN-GROESSE (vom User gemeldet): S und M kamen gleich gross raus.
Ursache war eine Luecke im Design, nicht in der Bedienung: "keep_size"
normiert die Zufalls-Streuung auf konstantes Volumen - damit wurde alles,
was ueber scale_min/max eingestellt war, wieder herausgerechnet. Fuer die
Groesse der KLASSE gab es schlicht keinen Regler.
Neu: CLASS_SIZES (S=0.45, M=1.0, L=1.9, XL=3.2) plus "Groesse aus Klasse"
und eine freie "Basis-Groesse". Die Klassengroesse ist bewusst gesetzt und
von der Streuung getrennt; sie greift auch bei abgeschalteter Randomisierung.
Gemessen: S 0.85 m | M 1.9 m | L 3.5 m | XL 6.0 m, Volumen-Streuung
INNERHALB jeder Klasse bleibt bei 1.11x (nur Form).
2) BUG im Zufalls-Button (kam mit 2.5.0): jitter() schrieb einen float in die
int-Property subsurf_levels -> TypeError, der Operator brach komplett ab.
jitter() rundet jetzt fuer Int-Properties. Von tests/test_randomize.py
gefangen.
Neuer Test tests/test_rock_classes.py deckt die Klassengroessen ab.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Blatt-Card waehlen -> wird auf die duennen Zweige gestreut (Filter ueber das
Radius-Attribut des Meshes) und als <Name>_Leaf mit identischem Ursprung abgelegt.
Regler fuer Dichte, Groesse, Streuung, Kippung, Zweig-Schwelle. Der Button meldet
Stamm + Blatt = Summe und warnt bei Ueberschreitung des Tri-Budgets.
Gemessen: Eiche 1428 + Blattwerk 1032 = 2460 von 2500 Tris bei Default-Dichte 150.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Aeste kruemmen sich zur Spitze hin zur Senkrechten - als echte Rotation um den
Astansatz, nicht als Z-Versatz. Die Astlaenge bleibt dabei erhalten (gemessen:
+0.08 m Hoehe statt +0.93 m beim alten Droop-Hack). Pro Preset abgestimmt.
Idee aus UE 5.8 Procedural Vegetation / Weber-Penn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Sub Count = 0" schaltete die Sub-Aeste nicht ab (Curve to Points klemmt auf 1) ->
duenne "Nadeln" bei Palme und Kaktus. Jetzt echter Geometry-Switch.
Palme am TreeIt-Referenzbaum ausgemessen: kurze Wedelstiele (0.78 m statt 3.4 m),
Stamm 4.4 m mit dickem Kronenschaft. 854 statt 1784 Tris.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Palmenstamm mit nahezu konstantem Durchmesser (Taper 0.22), glatt geschwollener
Fuss ohne Wurzelbretter, 15 kraeftige Wedelstiele im Bogen statt duenner
haengender Draehte. 1784 von 2500 Tris.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rock Generator 2.5.0:
- "Zufaellig"-Button: wuerfelt Seed und Form-Parameter, "Streuung" steuert die
Abweichung von den aktuellen Werten (0 = nur neuer Seed). Optional werden auch
Basis-Form und Noise-Typ gewuerfelt. Werte werden auf ihre Grenzen geklemmt.
- Skalierung streut jetzt die FORM, nicht die Groesse: nicht-uniform ist Default,
und "Gesamtgroesse konstant halten" normiert das geometrische Mittel auf 1.
Gemessen ueber 6 Rocks: Volumen-Spanne 1.92x -> 1.11x. Wichtig, weil EcoGame
die Groesse zusaetzlich ueber ScaleMin/Max streut.
Tree Generator 1.19.0:
- "Zufaellig"-Button: neuer Seed plus Streuung auf Hoehe, Radius, Biegung,
Ast-Anzahl/-Laenge, Kronenform und Wurzelanlauf. Preset-Charakter bleibt, weil
relativ zu den aktuellen Werten gestreut wird (gemessen: Hoehe 6.65 -> 8.34 m).
- "_Leaf"- und "_Frucht"-Ebenen: leere Objekte mit IDENTISCHEM Ursprung neben dem
Baum (gemessen: Abstand 0.000000), damit die Ebenen in UE deckungsgleich sitzen.
Vier Test-Suiten liegen jetzt unter tests/ statt nur im Scratchpad.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fix: smart_project/dissolve_limited angle_limit jetzt in Radiant (Blender 4.x/5.x)
- Fix: Lightmap-UV-Kanal vor lightmap_pack aktiv setzen (UV0 nicht mehr ueberschrieben)
- Fehlerbehandlung pro bpy.ops-Call; einzelner Rock-Fehler bricht Batch nicht ab
- Modaler Batch-Modus mit Progress-Bar (ab modal_threshold)
- Bevel + Weighted Normal Toggle
- LOD-Set als fbx_type=LodGroup-Gruppe fuer Unreal
- Preset-System (JSON) + eingebaute Presets
- Platzhalter-Material-Zuweisung
- Headless-Testskript (tests/test_rock_gen.py)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>