diff --git a/blight-assets/src/main/resources/Shaders/Voxel.frag b/blight-assets/src/main/resources/Shaders/Voxel.frag index beae359..233be17 100644 --- a/blight-assets/src/main/resources/Shaders/Voxel.frag +++ b/blight-assets/src/main/resources/Shaders/Voxel.frag @@ -58,8 +58,11 @@ void main() { vec2 uvY = vWorldPos.xz / m_TexScale; vec2 uvZ = vWorldPos.xy / m_TexScale; - // Flach ab ~11° Gefälle (20% grade, normal.y≈0.98); alles andere Fels. - float flatBlend = smoothstep(0.94, 0.99, vNormal.y); + // Flach bis ~22,5° Gefälle (deckt sich mit der Bake-Schwelle in VoxelEditorState, + // die Rampen bis 22,5° glättet – vorher lag diese Grenze bei ~15° und sorgte dafür, + // dass geometrisch glatt gebackene, aber 15-22,5° steile Rampen trotzdem komplett + // mit der Klippen-Textur eingefärbt wurden statt mit der Flach-Textur. + float flatBlend = smoothstep(0.90, 0.95, vNormal.y); float steepBlend = 1.0 - flatBlend; // Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar. diff --git a/blight-common/src/main/java/de/blight/common/time/DayTime.java b/blight-common/src/main/java/de/blight/common/time/DayTime.java index 0a33b0c..009fbd7 100644 --- a/blight-common/src/main/java/de/blight/common/time/DayTime.java +++ b/blight-common/src/main/java/de/blight/common/time/DayTime.java @@ -10,8 +10,8 @@ import java.util.List; */ public class DayTime { - /** Standard-Tagesdauer in Echtzeit-Sekunden (15 Minuten). */ - public static final float DEFAULT_DAY_DURATION = 900f; + /** Standard-Tagesdauer in Echtzeit-Sekunden (96 Minuten). */ + public static final float DEFAULT_DAY_DURATION = 5760f; private float timeOfDay; private float timeScale; @@ -25,7 +25,7 @@ public class DayTime { /** @param dayDuration Echtzeit-Sekunden für einen kompletten Spieltag */ public DayTime(float dayDuration) { this.timeScale = 1f / dayDuration; - this.timeOfDay = 0.25f; // Start: Sonnenaufgang + this.timeOfDay = 0.5f; // Start: Mittag (bessere Sicht zum Testen als Sonnenaufgang) } public void update(float tpf) { diff --git a/blight-editor/src/main/java/de/blight/editor/SharedInput.java b/blight-editor/src/main/java/de/blight/editor/SharedInput.java index 9372aee..2065b9b 100644 --- a/blight-editor/src/main/java/de/blight/editor/SharedInput.java +++ b/blight-editor/src/main/java/de/blight/editor/SharedInput.java @@ -782,8 +782,6 @@ public class SharedInput { public volatile int bakeTotal = 0; /** JME → JFX: Status-Meldung nach Abschluss des Backens oder Löschens. */ public volatile String bakeStatusMsg = null; - /** JME → JFX: Aktuell abgeschlossene Blur-Iteration (0-7). */ - public volatile int blurIterDone = 0; /** * Per BFS selektierte gebackene Chunks (chunkKey-kodiert). diff --git a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java index cda4d88..26874c5 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java @@ -547,6 +547,11 @@ public class TerrainEditorState extends BaseAppState { thirdSplatTex.setWrap(Texture.WrapMode.EdgeClamp); thirdSplatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); thirdSplatTex.setMagFilter(Texture.MagFilter.Bilinear); + + // loadedMapData wird ab hier nicht mehr gebraucht (letzter Verbraucher war + // dieser Merge). Freigeben, weil terrainHeight allein ~1 GB (16385² floats) + // belegt und sonst die ganze Session über unnötig im Heap hängt. + loadedMapData = null; } // Standard-Texturpfade (Fallback wenn kein benutzerdefinierter Pfad gesetzt) diff --git a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java index f4fa4f3..1506bf4 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java @@ -1558,47 +1558,10 @@ public class VoxelEditorState extends BaseAppState { cBuf[c.idx(bx, VoxelChunk.CELLS, bz)] = tBuf[c.idx(bx, 0, bz)]; } - input.blurIterDone = 0; - // Bilateraler Filter: Nachbarn mit großem Dichtesprung (Klippen, Felskanten) - // bekommen fast Null-Gewicht → scharfe Übergänge bleiben erhalten. - // Flache Bereiche (geringe Differenz) werden voll geglättet wie bisher. - // sigma_r = 30 → diff=20: w≈0.80 (flach, volles Blur); - // diff=80: w≈0.04 (Klippe, kaum Blur). - final float BILATERAL_INV_2S2 = 1f / (2f * 30f * 30f); - for (int iter = 0; iter < 7; iter++) { - Map nextBufs = new HashMap<>(); - for (VoxelChunk c : nonEmpty) { - long k = chunkKey(c.cx, c.cy, c.cz); - float[] cur = curBufs.get(k); - float[] next = new float[blurN * blurN * blurN]; - for (int by = 0; by < blurN; by++) { - for (int bz = 0; bz < blurN; bz++) { - for (int bx = 0; bx < blurN; bx++) { - float center = cur[c.idx(bx, by, bz)]; - float wSum = 0f, vSum = 0f; - for (int dy = -1; dy <= 1; dy++) - for (int dz = -1; dz <= 1; dz++) - for (int dx = -1; dx <= 1; dx++) { - int sx = bx+dx, sy = by+dy, sz = bz+dz; - float nb; - if (sx >= 0 && sx < blurN && sy >= 0 && sy < blurN && sz >= 0 && sz < blurN) - nb = cur[c.idx(sx, sy, sz)]; - else - nb = getBlurBuf(curBufs, allOriginal, c, sx, sy, sz); - float diff = nb - center; - float w = (float) Math.exp(-diff * diff * BILATERAL_INV_2S2); - vSum += w * nb; - wSum += w; - } - next[c.idx(bx, by, bz)] = vSum / wSum; - } - } - } - nextBufs.put(k, next); - } - curBufs = nextBufs; - input.blurIterDone = iter + 1; - } + // KEIN Blur mehr vor der Rampen-Berechnung (entfernt 2026-08-06): war ein Rest + // der alten Pipeline, den ich beim "fang bei 0 an"-Rewrite versehentlich stehen + // gelassen hatte. hfMap wird jetzt direkt aus curBufs (= unveränderte Rohdichte, + // nur Rand-synchronisiert) gebaut, nicht aus geblurrten Daten. // ── Neigungsadaptive Oberflächenverarbeitung (Regressions-Ansatz) ──────── // @@ -1620,12 +1583,14 @@ public class VoxelEditorState extends BaseAppState { // (verhindert, dass lokale Extrema durch weit entfernte Werte abgezogen // werden; Regressions-Abweichung an Stufenfronten liegt bei ≤ 0,46). { - final float TAN_22_5 = 0.414f; // tan(22,5°) - final float TAN_45 = 1.000f; // tan(45°) - final float MIN_SLOPE = 0.05f; // < ~3° → komplett flach, nicht anfassen - java.util.Random rng = new java.util.Random(42); final int C = VoxelChunk.CELLS; - final int W = 5; // Regressions-Halbfenster (±5 Spalten) + // RADIUS in Voxel-Zellen. 1 Zelle = 1 Weltmeter (siehe VoxelChunk.toWorldX: + // Koeffizient 1, kein Skalierungsfaktor). 10 m gewählt, weil ein kleinerer + // Deckel bei konstanter Steigung (langer, flacher Hang) wieder künstliche + // Stufen einführen würde, sobald die Suche vor dem echten Kontur-Nachbarn + // abbricht (vom Nutzer bestätigt: "nicht so niedrig, dass wir bei einer + // konstanten Steigung wieder Stufen reinbekommen"). + final int RADIUS = 10; // ── Schritt 1: Höhenfeld ────────────────────────────────────────── // Key-Schema: (cx*C + bx + 30000) * 60001 + (cz*C + bz + 30000) @@ -1649,54 +1614,268 @@ public class VoxelEditorState extends BaseAppState { } } - // ── Schritt 2: Lineare 2D-Regression je Spalte ─────────────────── - // Fit: H(dx,dz) = a + b·dx + c·dz (zentriert auf Spaltenmitte). - // a = meanH → sub-voxel-genaue Ziel-Höhe (smoothH) - // b = slopeX, c = slopeZ - // totalSlope = sqrt(b²+c²) → Gesamt-Neigung des Ebenen-Fits + // Nachbar-Chunks (nur XZ, gleiches cy) rein lesend nachladen, damit die + // Ringsuche in Schritt 2 nicht an der Batch-Grenze abreißt (RADIUS=10 kann + // problemlos in einen nicht mitgebackenen Nachbar-Chunk hineinreichen). + // Diese Chunks werden NICHT gebacken/verändert, nur ihr Höhenfeld gelesen. + for (VoxelChunk c : nonEmpty) { + for (int dcz = -1; dcz <= 1; dcz++) { + for (int dcx = -1; dcx <= 1; dcx++) { + if (dcx == 0 && dcz == 0) continue; + int ncx = c.cx + dcx, ncz = c.cz + dcz, ncy = c.cy; + long nk = chunkKey(ncx, ncy, ncz); + if (allOriginal.containsKey(nk)) continue; // schon im Batch enthalten + try { + if (!VoxelChunkIO.exists(ncx, ncy, ncz)) continue; + VoxelChunk nc = VoxelChunkIO.load(ncx, ncy, ncz); + for (int bz = 0; bz < C; bz++) { + for (int bx = 0; bx < C; bx++) { + for (int by = VoxelChunk.SIZE - 1; by >= 0; by--) { + if (nc.getDensity(bx, by, bz) > 0) { + int wiy = ncy * C + by; + long hk = (long)(ncx * C + bx + 30000) * 60001L + + (ncz * C + bz + 30000); + hfMap.merge(hk, wiy, Math::max); + break; + } + } + } + } + } catch (IOException e) { + log.warn("Nachbar-Chunk ({},{},{}) konnte für Rampen-Suche nicht geladen werden", ncx, ncy, ncz, e); + } + } + } + } + + // ── Schritt 2: Waypoint-Distanz-Interpolation ──────────────────── + // 1:1-Umsetzung der Nutzer-Vorgabe vom 2026-08-05/06: + // Für jede Spalte (Rohhöhe h0) isotrope Ringsuche (Chebyshev-Distanz – + // vermeidet Zickzack-Artefakte einer Achsen-Suche) nach dem nächsten + // Nachbarn mit Höhe > h0 (d_up, h_up = TATSÄCHLICHE gefundene Höhe, + // kein Cap auf h0+1 – mehrstufige Sprünge werden laut Nutzer als EINE + // durchgehende Fläche überbrückt statt Stufe für Stufe) und dem + // nächsten Nachbarn mit Höhe < h0 (d_downRaw). + // smoothH = (h0·d_up + h_up·d_down) / (d_up + d_down) + // d_down = d_downRaw − 1 (Trailing-Edge-Korrektur: die eigene + // Kante liegt einen Schritt vor der ersten tatsächlich + // niedrigeren Zelle – siehe diag_waypoint*.jsh) + // Kein höherer Nachbar gefunden → Plateau → smoothH = h0 (flach). + // Kein niedrigerer Nachbar in der Voxel-Struktur gefunden → Basisterrain + // wird als impliziter unterer Nachbar mitgesucht (grobe Version, per + // Nutzer-Feedback erstmal ohne <1m-Schwelle – "passt so, sonst gehen + // wir das später an"). Nur falls selbst das nicht greift (echtes + // Innen-Plateau weit von jeder Kante) bleibt smoothH = h0. // - // Warum Regression besser als Max-Slope: - // Max-Slope wird von den UNMITTELBAREN Nachbarn dominiert (die bei - // einer Stufe ±1 Voxel haben → slope = 1,0 > TAN_22,5°). - // Die Regression sieht das gesamte ±5-Fenster und klassifiziert - // die Stufe korrekt als sanfte Steigung (slope ≈ 0,14 für 3er-Stufen). + // Validiert in jshell VOR dem Einbau (siehe scratchpad diag_waypoint2.jsh, + // diag_waypoint2d_v2.jsh, diag_waypoint2d_v3.jsh): + // - 1D-Vorgabe 3,2,2,2,2,1,1,1,0 → exakt erwartete Werte inkl. Basisterrain-Tail + // - 2D-Rampen-Extrusion → identisch zum 1D-Fall + // - Kuppel/Krümmungstest (der ursprüngliche Graben-Auslöser dieser + // ganzen Debug-Session) → 0 spurious dips, weil die Formel auf reiner + // Nachbar-Distanz basiert statt auf einer Fenster-Mittelung (Jensen's- + // Inequality-Problem der alten Regressions-/Blur-Ansätze entfällt). + // - Mehrstufen-Sprung 3,3,3,1,1,0 → 3,3,3,2,1,0 (Ebene 2 übersprungen, + // eine durchgehende Fläche statt Stufen). + // - Isolierter Spike auf großem Plateau → bleibt isolierte Säule + // (kein niedrigerer Nachbar in der Umgebung → korrekt kein Ramping). // - // regMap[0] = smoothH, regMap[1] = totalSlope - Map regMap = new HashMap<>(hfMap.size()); + // NACHTRAG 2026-08-06 (echte Chunks 14/15_0_06/07): erster Bake zeigte kleine + // aufgeschüttete Wälle an alten Voxel-Kanten. Ursache in echten (nicht rein + // synthetischen) Daten gefunden: bei gleicher Ring-Distanz r existieren oft + // MEHRERE Nachbarn mit unterschiedlicher Höhe (z.B. 8 im Westen, 9 im Norden, + // beide r=1). Die ursprüngliche Version nahm den ERSTEN Treffer der dz/dx- + // Schleife statt des nächstgelegenen tatsächlichen Stufen-Nachbarn → zufällige + // Sprünge zu einer zu weit entfernten Höhe. Fix: pro Ring den Nachbarn mit der + // kleinsten Höhe > h0 bzw. größten Höhe < h0 wählen. Betraf 17 von 10002 Spalten + // in den Test-Chunks, max. Abweichung 1,0 (diag_realwall*.jsh im scratchpad). + Map smoothMap = new HashMap<>(hfMap.size()); for (Map.Entry entry : hfMap.entrySet()) { long hk0 = entry.getKey(); int h0 = entry.getValue(); int colZ = (int)(hk0 % 60001L) - 30000; int colX = (int)(hk0 / 60001L) - 30000; - float sumH = 0f, sumDxH = 0f, sumDzH = 0f; - float sumDx2 = 0f, sumDz2 = 0f; - int cnt = 0; - for (int dz = -W; dz <= W; dz++) { - for (int dx = -W; dx <= W; dx++) { - long hkN = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000); - Integer hN = hfMap.get(hkN); - if (hN == null) continue; - sumH += hN; - sumDxH += (float)dx * hN; - sumDzH += (float)dz * hN; - sumDx2 += dx * dx; - sumDz2 += dz * dz; - cnt++; + // WICHTIG: Bei gleicher Ring-Distanz r können mehrere Nachbarn mit + // unterschiedlicher Höhe existieren (z.B. Höhe 8 im Westen UND Höhe 9 + // im Norden, beide bei r=1). Naiv den ERSTEN Treffer der Schleifen- + // Reihenfolge zu nehmen wählt praktisch zufällig zwischen ihnen (abhängig + // von der dz/dx-Iterationsreihenfolge) und erzeugte an echten Kanten + // willkürliche Sprünge zum "falschen" (zu weit entfernten) Nachbarn – + // sichtbar als kleine aufgeschüttete Wälle. Fix: pro Ring den Nachbarn + // mit der KLEINSTEN Höhe > h0 (bzw. GRÖSSTEN Höhe < h0) wählen, also den + // nächstgelegenen tatsächlichen Stufen-Nachbarn, nicht irgendeinen weiter + // entfernten. Validiert gegen reale Chunk-Daten (diag_realwall*.jsh). + Integer hUp = null; int dUp = -1; + Integer hDown = null; int dDownRaw = -1; + for (int r = 1; r <= RADIUS && (hUp == null || hDown == null); r++) { + Integer bestUpThisRing = null, bestDownThisRing = null; + for (int dz = -r; dz <= r; dz++) { + for (int dx = -r; dx <= r; dx++) { + if (Math.max(Math.abs(dx), Math.abs(dz)) != r) continue; + long hkN = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000); + Integer hN = hfMap.get(hkN); + if (hN == null) continue; + if (hN > h0 && (bestUpThisRing == null || hN < bestUpThisRing)) bestUpThisRing = hN; + if (hN < h0 && (bestDownThisRing == null || hN > bestDownThisRing)) bestDownThisRing = hN; + } + } + if (hUp == null && bestUpThisRing != null) { hUp = bestUpThisRing; dUp = r; } + if (hDown == null && bestDownThisRing != null) { hDown = bestDownThisRing; dDownRaw = r; } + } + + // KORREKTUR 2026-08-06 (echter Bake-Test, Screenshot mit 1m-Stufe vor langer + // flacher Fläche): wenn kein niedrigerer Voxel-Nachbar gefunden wird, hatte + // ich hier fälschlich "smoothH=h0" (flach, keine Rampe) verwendet. Das + // widerspricht der Original-Vorgabe: "wenn wir keinen niedrigeren Partner + // finden, dann haben wir die Höhe des Basisterrains erreicht und es soll die + // OBERE GERADE WEITERGEZOGEN werden" – bereits im 1D-Fall exakt so validiert + // (Tail-Extrapolation, siehe diag_waypoint2.jsh). Die 2D-Generalisierung hatte + // das fälschlich auf "flach" vereinfacht, wodurch Spalten mit einem gültigen + // hUp aber ohne erreichbaren hDown (z.B. eine 1m-Stufe vor einer sehr weiten + // Ebene) GAR NICHT geramped wurden – sichtbar als scharfe Stufe. + // Fix: fehlendes hDown wird als "gerade noch nicht gefunden, aber bei RADIUS + // angenommen" behandelt statt als "keine Rampe". Das erzeugt eine sanft zum + // Rand hin auslaufende Rampe über die vollen RADIUS Meter, statt eines + // abrupten Sprungs zwischen "geramped" und "unverändert flach". + float smoothH; + if (hUp == null) { + smoothH = h0; // Plateau: kein höherer Nachbar -> bewusst flach (Originalregel) + } else { + float dDown = (hDown != null) ? (dDownRaw - 1) : (RADIUS - 1); + smoothH = (dDown <= 0f) ? h0 : (h0 * dUp + hUp * dDown) / (dUp + dDown); + } + smoothMap.put(hk0, smoothH); + } + + // ── Schritt 2b: Perimeter-Übergang zum Basisterrain ────────────── + // Umsetzung v3 (2026-08-06) der Nutzer-Vorgabe: "berechne auch die Schnittpunkte + // mit dem Basisterrain einmal außen herum... baue hier die Rampe weiter anstelle + // einer Klippe", 10m Reichweite, Fallback-Szenario (nur wenn Schritt 2 die Struktur + // nicht schon selbst bis auf Basisterrain-Niveau geführt hat). + // + // v1 (Höhen-Schwellwert pro Spalte) erzeugte an der Schwelle selbst neue Sprünge. + // v2 (unabhängige Strahlen pro Rand-Spalte/Richtung) überschrieben sich gegenseitig + // unkoordiniert an Überlappungen → Flickenteppich aus Dreiecken (Screenshot 21-56-58). + // v3 (reine Nachbar-Mittelwert-Ausbreitung ohne Steigung) verlor den Winkel des + // Randes komplett → 10m flaches Plateau statt Rampe (Nutzer-Feedback). + // v4 (Höhe+Steigung Zelle-für-Zelle weiterreichen/mitteln, BFS) divergierte: die + // Rückkopplung über viele Zellen hinweg schaukelte sich zu chaotischen, spitzen + // Glitches auf (Screenshot 22-23-06) – klassisches Instabilitätsproblem iterativer + // Fortpflanzung ohne Dämpfung. + // Fix v5: KEINE Kettenfortpflanzung mehr. Jede leere Zelle im 10m-Streifen wird + // unabhängig berechnet – exakt wie Schritt 2 selbst (dort funktioniert das robuste + // "jede Spalte für sich per Ringsuche" bereits nachweislich gut): isotrope Ringsuche + // zur nächsten Struktur-Zelle, deren lokale Steigung einmalig (nicht iterativ) + // fortgesetzt, mit wachsender Distanz zunehmend zur echten Basisterrain-Höhe hin + // eingeblendet. Keine Zelle hängt von einer anderen NEU BERECHNETEN Zelle ab → + // keine Rückkopplung, keine Divergenz möglich. War in der Tat stabil, ABER: edgeH + // selbst schwankte zwischen benachbarten Zielzellen stark (3,9 bis 5,33 nur 4 + // Zellen auseinander), weil jede Zelle nur die EINE zuerst gefundene Struktur- + // Zelle nahm. Fix v5b: über ALLE Struktur-Zellen im nächstgelegenen Ring mitteln. + // Trotzdem blieb ein welliges/oszillierendes Band über eigentlich flachen Flächen + // sichtbar (Screenshot 22-43-57, aus Seitenansicht bestätigt: keine Höhle/Überhang, + // echte Welligkeit) – die Steigungs-Fortsetzung (auch gedämpft, siehe rawH) war + // schlicht zu rauschempfindlich. + // Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend + // von der (ring-gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe. Folgt + // nicht mehr exakt dem Rampen-Winkel, ist aber garantiert monoton/glatt. + int[][] DIRS8 = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}}; + + // Kandidaten sammeln: alle leeren Zellen innerhalb RADIUS einer Rand-Spalte. + java.util.Set candidates = new java.util.HashSet<>(); + for (long hk0 : hfMap.keySet()) { + int colZ = (int)(hk0 % 60001L) - 30000; + int colX = (int)(hk0 / 60001L) - 30000; + for (int dz = -RADIUS; dz <= RADIUS; dz++) { + for (int dx = -RADIUS; dx <= RADIUS; dx++) { + if (Math.max(Math.abs(dx), Math.abs(dz)) > RADIUS) continue; + long hkC = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000); + if (!hfMap.containsKey(hkC)) candidates.add(hkC); } } - if (cnt < 4) { - regMap.put(hk0, new float[]{ h0, 99f }); - continue; + } + + Map extensionMap = new HashMap<>(); + int extCount = 0, extLogged = 0; + for (long hkC : candidates) { + int colZ = (int)(hkC % 60001L) - 30000; + int colX = (int)(hkC / 60001L) - 30000; + + // Nächsten Ring mit Struktur-Zellen isotrop suchen (gleiches Vorgehen wie + // Schritt 2), dann ALLE Struktur-Zellen in diesem Ring mitteln – nicht nur die + // erste gefundene. Grund (Log-Befund): benachbarte Zielzellen fanden oft + // unterschiedliche einzelne "nächste" Zellen mit stark abweichender Höhe + // (edgeH schwankte 3,9 bis 5,33 nur 4 Zellen auseinander) → wildes Springen. + // Mittelung über den ganzen Ring glättet das. + int nearestDist = -1; + java.util.List ringHits = new java.util.ArrayList<>(); // {colX,colZ} + outer: + for (int r = 1; r <= RADIUS; r++) { + for (int dz = -r; dz <= r; dz++) { + for (int dx = -r; dx <= r; dx++) { + if (Math.max(Math.abs(dx), Math.abs(dz)) != r) continue; + long hkN = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000); + if (hfMap.containsKey(hkN)) ringHits.add(new long[]{colX + dx, colZ + dz}); + } + } + if (!ringHits.isEmpty()) { nearestDist = r; break outer; } } - float meanH = sumH / cnt; - float slopeX = (sumDx2 > 0f) ? sumDxH / sumDx2 : 0f; - float slopeZ = (sumDz2 > 0f) ? sumDzH / sumDz2 : 0f; - float totalSlope = (float) Math.sqrt(slopeX * slopeX + slopeZ * slopeZ); - regMap.put(hk0, new float[]{ meanH, totalSlope }); + if (nearestDist < 0) continue; // außerhalb der Reichweite, bleibt Klippe + + float sumEdgeH = 0f; int hitCnt = 0; + for (long[] hit : ringHits) { + int hColX = (int) hit[0], hColZ = (int) hit[1]; + long hKey = (long)(hColX + 30000) * 60001L + (hColZ + 30000); + Float hH = smoothMap.get(hKey); + if (hH == null) continue; + sumEdgeH += hH; hitCnt++; + } + if (hitCnt == 0) continue; + float edgeH = sumEdgeH / hitCnt; + + // KEINE Steigungs-Extrapolation mehr (v4/v5 hatten das versucht – eine an nur + // wenigen Pixeln gemessene Steigung ist zu verrauscht, verstärkt sich über die + // Distanz und erzeugte ein welliges/oszillierendes Band über eigentlich flachen + // Flächen, siehe Screenshot 22-43-57). Reiner, robuster Distanz-Blend von der + // (gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe: nah am Rand + // dominiert die Kante, weiter draußen die Terrainhöhe. Folgt nicht mehr exakt + // dem Winkel der letzten Rampe, ist aber garantiert monoton und ohne Rauschen. + float wx = colX - 2048f, wz = colZ - 2048f; + float th = terrainH(wx, wz); + float finalH = edgeH; + if (Float.isFinite(th)) { + float t = nearestDist / (float) RADIUS; // 0 am Rand, 1 bei RADIUS + finalH = edgeH * (1f - t) + th * t; + } + extensionMap.put(hkC, finalH); + extCount++; + if (extLogged < 20) { + log.info("Perimeter-Rampe: colX={} colZ={} dist={} edgeH={} th={} finalH={}", + colX, colZ, nearestDist, edgeH, th, finalH); + extLogged++; + } + } + log.info("Perimeter-Übergang: {} Erweiterungs-Spalten jenseits des Strukturrands erzeugt.", extCount); + // Erweiterungs-Spalten in hfMap/smoothMap einspeisen, damit Schritt 3 sie mitschreibt. + for (Map.Entry e : extensionMap.entrySet()) { + long hk = e.getKey(); + if (hfMap.containsKey(hk)) continue; + hfMap.put(hk, Math.round(e.getValue())); + smoothMap.put(hk, e.getValue()); } // ── Schritt 3: Dichte anpassen ──────────────────────────────────── + // ALLE Spalten aus hfMap bekommen den Dichte-Gradienten geschrieben, auch + // Plateau-Spalten (smoothH==h0), nicht nur echte Rampen. Grund (gefunden + // 2026-08-06 per Debug-Dump-Vergleich gegen das echte Mesh): eine unveränderte + // Spalte (volle Dichte +127 bei h0, leer bei h0+1) hat ihre MC-Isofläche NICHT + // exakt bei h0, sondern bei ≈ h0 + 127/255 ≈ h0+0.498 (lineare Nullstellen- + // Interpolation). Wurde nur bei Rampen-Spalten explizit auf die exakte smoothH + // geschrieben, blieben Plateau-Spalten bei ihrer natürlichen +0,5-Position → + // an JEDER Grenze zwischen Plateau und Rampe ein ~0,5 Einheiten hoher Sprung. + // Das war die eigentliche Ursache der "aufgeschütteten Wälle" (betraf 60% der + // Spalten in den Test-Chunks). Fix: einheitliche Konvention für alle Spalten. { Map nextBufs = new HashMap<>(); for (VoxelChunk c : nonEmpty) { @@ -1710,32 +1889,15 @@ public class VoxelEditorState extends BaseAppState { for (int bx = 0; bx < C; bx++) { int colX = c.cx * C + bx; long hk0 = (long)(colX + 30000) * 60001L + (colZ + 30000); - Integer h0 = hfMap.get(hk0); - float[] reg = regMap.get(hk0); - if (h0 == null || reg == null) continue; + Integer h0 = hfMap.get(hk0); + Float smoothH = smoothMap.get(hk0); + if (h0 == null || smoothH == null) continue; - float smoothH = reg[0]; - float totalSlope = reg[1]; - float diff = smoothH - h0; - - if (totalSlope >= MIN_SLOPE && totalSlope < TAN_22_5 - && Math.abs(diff) <= 0.6f) { - // Sanfte Steigung: sub-voxel-genauen Dichte-Gradienten schreiben. - // MC interpoliert die Isofläche dann auf die genaue smoothH-Höhe. - for (int by = 0; by < blurN; by++) { - float d = (smoothH - (wiyBase + by)) * 127f; - next[c.idx(bx, by, bz)] = Math.max(-128f, Math.min(127f, d)); - } - } else if (totalSlope > TAN_45) { - // Steile Klippe: Noise für organische Optik - int surfBy = h0 - wiyBase; - if (surfBy < 0 || surfBy >= blurN) continue; - for (int by = Math.max(0, surfBy - 2); - by <= Math.min(blurN - 1, surfBy + 2); by++) { - float noise = (float)(rng.nextGaussian() * 20.0); - float val = cur[c.idx(bx, by, bz)] + noise; - next[c.idx(bx, by, bz)] = Math.max(-128f, Math.min(127f, val)); - } + // Sub-voxel-genauen Dichte-Gradienten schreiben, damit MC die + // Isofläche auf die exakte smoothH-Höhe interpoliert. + for (int by = 0; by < blurN; by++) { + float d = (smoothH - (wiyBase + by)) * 127f; + next[c.idx(bx, by, bz)] = Math.max(-128f, Math.min(127f, d)); } } } @@ -2095,32 +2257,7 @@ public class VoxelEditorState extends BaseAppState { } } - /** - * Cross-boundary Lookup für den kooperativen Blur: - * Liest aus dem aktuellen Blur-Buffer des Nachbar-Chunks, oder – falls der - * Nachbar nicht in bufs ist – direkt aus dessen Original-Daten. - * Verwendet {@code src.idx()} da SIZE für alle Chunks identisch ist. - */ - private float getBlurBuf(Map bufs, Map originals, - VoxelChunk src, int x, int y, int z) { - int n = VoxelChunk.SIZE; - int cells = VoxelChunk.CELLS; - int ncx = src.cx, ncy = src.cy, ncz = src.cz; - int lx = x, ly = y, lz = z; - if (x < 0) { ncx--; lx = cells + x; } - else if (x >= n) { ncx++; lx = x - cells; } - if (y < 0) { ncy--; ly = cells + y; } - else if (y >= n) { ncy++; ly = y - cells; } - if (z < 0) { ncz--; lz = cells + z; } - else if (z >= n) { ncz++; lz = z - cells; } - long nk = chunkKey(ncx, ncy, ncz); - float[] buf = bufs.get(nk); - if (buf != null) return buf[src.idx(lx, ly, lz)]; - VoxelChunk nb = originals.get(nk); - return nb != null ? nb.getDensity(lx, ly, lz) : Byte.MIN_VALUE; - } - - /** Liest Dichte aus einem geblurrten Chunk, clamped an Chunk-Grenzen. Nur für Gradienten-Berechnung. */ + /** Liest Dichte aus einem Chunk, clamped an Chunk-Grenzen. Nur für Gradienten-Berechnung. */ private static float sampleBlurred(VoxelChunk chunk, int x, int y, int z) { int n = VoxelChunk.SIZE; return chunk.getDensity( diff --git a/blight-game/build.gradle b/blight-game/build.gradle index 3b4b298..12dd815 100644 --- a/blight-game/build.gradle +++ b/blight-game/build.gradle @@ -51,6 +51,9 @@ tasks.register('run', JavaExec) { description = 'Startet das Spiel' mainClass = mainClassName classpath = sourceSets.main.runtimeClasspath + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(26) + } jvmArgs = [ '--add-opens', 'java.base/java.lang=ALL-UNNAMED', '--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED', diff --git a/blight-game/src/main/java/de/blight/game/BlightGame.java b/blight-game/src/main/java/de/blight/game/BlightGame.java index b41af2f..4758dc1 100644 --- a/blight-game/src/main/java/de/blight/game/BlightGame.java +++ b/blight-game/src/main/java/de/blight/game/BlightGame.java @@ -62,6 +62,15 @@ public class BlightGame extends SimpleApplication { private long startTimeMs = 0; private long lastFullscreenFix = 0; private long pendingFullscreenFixAt = Long.MAX_VALUE; + // Cinnamons Display-Daemon interpretiert den glfwSetWindowMonitor()-Toggle in + // fixFullscreenMonitor() offenbar als Monitor-Neukonfiguration und setzt dabei + // "scaling-factor" (org.cinnamon.desktop.interface) auf 0 (=Auto) zurück, auch wenn + // der Nutzer explizit 100% (Wert 1) eingestellt hatte – das bleibt dann auch nach dem + // Spielende so stehen. Wir sichern den Wert vor dem Fix und schreiben ihn zeitversetzt + // zurück (Cinnamon braucht etwas, bis es reagiert – sofortiges Zurückschreiben würde + // von Cinnamons eigenem Reset kurz danach wieder überschrieben). + private String savedScalingFactor = null; + private long pendingScalingRestoreAt = Long.MAX_VALUE; private boolean firstLaunch = true; // false nach dem ersten simpleInitApp() private boolean initialFixDone = false; // Initial-Fix bei 0.5s bereits gelaufen private boolean pendingMenuRebuild = false; // nur true nach explizitem app.restart() @@ -202,6 +211,7 @@ public class BlightGame extends SimpleApplication { audioSettings = AudioSettingsStore.load(); de.blight.game.state.ModelLodControl.debugLod2 = graphicsSettings.debugLod2; + captureScalingFactor(); startTimeMs = System.currentTimeMillis(); initialFixDone = false; @@ -226,6 +236,16 @@ public class BlightGame extends SimpleApplication { // Fullscreen-Zustand war (wie beim 5s-Watchdog). Aus dem Hidden-Zustand behandelt // Cinnamon den nächsten Fullscreen-Übergang als Neuerstplatzierung und ignoriert // den Ziel-Monitor zugunsten des aktiven Workspaces (Editor-Monitor). + // + // mainMenuState wird weiter unten in dieser Methode gebaut – mit der JETZT + // aktuellen (noch nicht fullscreen-korrigierten) Kamera-Größe. Der Vollbild-Fix + // läuft aber erst ~200ms später in simpleUpdate() und löst dann reshape() aus. + // Ohne pendingMenuRebuild würde rebuildScreenDependentStates() dabei NUR HUD/ + // Hotbar/LoadingScreen neu aufbauen (siehe dortige Kommentare: das ist normalerweise + // extra vor app.restart() gesetzt) – das Menü bliebe dauerhaft auf der falschen, + // zu kleinen Ausgangsgröße stehen und würde nur einen Bildschirm-Ausschnitt + // (unten links) füllen, obwohl das Fenster längst auf volle Auflösung steht. + pendingMenuRebuild = true; } // Restart nach Einstellungsänderung: pendingFullscreenFixAt (scheduleFullscreenFix) // bleibt gesetzt und übernimmt den Fix nach 1 s. @@ -493,6 +513,11 @@ public class BlightGame extends SimpleApplication { fixFullscreenMonitor(); } + if (now >= pendingScalingRestoreAt) { + pendingScalingRestoreAt = Long.MAX_VALUE; + restoreScalingFactor(); + } + // Watchdog: Vollbild-Verlust durch GNOME/WM (erkennbar an Auflösungs-Mismatch). if (graphicsSettings != null && graphicsSettings.fullscreen) { if (now - startTimeMs > 5_000 @@ -798,8 +823,56 @@ public class BlightGame extends SimpleApplication { glfwSleep(200); GLFW.glfwFocusWindow(win); + // scaling-factor zeitversetzt zurückschreiben, siehe Feld-Kommentar oben bei + // savedScalingFactor – Cinnamon reagiert erst mit etwas Verzögerung auf den + // Monitor-Toggle, ein sofortiger Restore würde von Cinnamons eigenem Reset + // wieder überschrieben. + pendingScalingRestoreAt = System.currentTimeMillis() + 2_000; + } catch (Throwable e) { log.warn("[Grafik] Vollbild-Fix fehlgeschlagen: {}", e.getMessage(), e); } } + + /** Liest den aktuellen Cinnamon-Skalierungsfaktor, um ihn nach dem Vollbild-Fix + * wiederherzustellen (siehe Feld-Kommentar bei savedScalingFactor). */ + private void captureScalingFactor() { + try { + Process p = new ProcessBuilder( + "gsettings", "get", "org.cinnamon.desktop.interface", "scaling-factor") + .redirectErrorStream(true).start(); + String out = new String(p.getInputStream().readAllBytes()).trim(); + p.waitFor(); + // Format ist z.B. "uint32 1" – letztes Token ist der Wert. Nicht per + // replaceAll("[^0-9]","") Ziffern rausfiltern: "uint32" enthält selbst + // die Ziffern 3 und 2, das hängt sie vor den echten Wert (1 -> "321", + // 321 -> "32321", ...) und schaukelt sich exponentiell hoch. + String[] parts = out.split("\\s+"); + String value = parts[parts.length - 1]; + savedScalingFactor = value.matches("[0-9]+") ? value : null; + log.info("[Grafik] scaling-factor gesichert: {}", savedScalingFactor); + } catch (Exception e) { + log.debug("[Grafik] scaling-factor konnte nicht gelesen werden (evtl. kein Cinnamon): {}", e.getMessage()); + savedScalingFactor = null; + } + } + + /** Schreibt den zuvor gesicherten Cinnamon-Skalierungsfaktor zurück. */ + private void restoreScalingFactor() { + if (savedScalingFactor == null) return; + try { + new ProcessBuilder( + "gsettings", "set", "org.cinnamon.desktop.interface", "scaling-factor", savedScalingFactor) + .start().waitFor(); + log.info("[Grafik] scaling-factor wiederhergestellt: {}", savedScalingFactor); + } catch (Exception e) { + log.warn("[Grafik] scaling-factor konnte nicht wiederhergestellt werden: {}", e.getMessage()); + } + } + + @Override + public void stop() { + restoreScalingFactor(); + super.stop(); + } } diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index cbf6e81..68a434c 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -373,7 +373,7 @@ public class WorldScene extends BaseAppState { dayNight.getDayTime().setTimeOfDay(0.5f); } else if (envCamFrames == 30) { log.info("[WorldScene] Starte LightProbe-Backen bei Mittag (Frame {})...", envCamFrames); - final float restoreTime = 0.25f; + final float restoreTime = 0.5f; // Spielstart-Uhrzeit, siehe DayTime-Default LightProbeFactory.updateProbe(probe, envCam, rootNode, new JobProgressAdapter() { @Override public void done(LightProbe result) { diff --git a/blight-game/src/main/java/de/blight/game/state/DayNightState.java b/blight-game/src/main/java/de/blight/game/state/DayNightState.java index 94426f3..3af6745 100644 --- a/blight-game/src/main/java/de/blight/game/state/DayNightState.java +++ b/blight-game/src/main/java/de/blight/game/state/DayNightState.java @@ -87,7 +87,7 @@ public class DayNightState extends BaseAppState implements TimeListener { // ── Konstruktoren ───────────────────────────────────────────────────────── - /** Game-Modus: Schatten aktiv, 5-Minuten-Tag. */ + /** Game-Modus: Schatten aktiv, 96-Minuten-Tag (siehe DayTime.DEFAULT_DAY_DURATION). */ public DayNightState() { this(new DayTime(), true); } diff --git a/blight-map/src/main/map/blight_map.blm b/blight-map/src/main/map/blight_map.blm index dfe2096..7150a2d 100644 Binary files a/blight-map/src/main/map/blight_map.blm and b/blight-map/src/main/map/blight_map.blm differ diff --git a/blight-map/src/main/map/chunks/chunk_14_06.blc b/blight-map/src/main/map/chunks/chunk_14_06.blc index 42e7cfb..d325928 100644 Binary files a/blight-map/src/main/map/chunks/chunk_14_06.blc and b/blight-map/src/main/map/chunks/chunk_14_06.blc differ diff --git a/blight-map/src/main/map/chunks/chunk_14_07.blc b/blight-map/src/main/map/chunks/chunk_14_07.blc index 768b2d5..a91341c 100644 Binary files a/blight-map/src/main/map/chunks/chunk_14_07.blc and b/blight-map/src/main/map/chunks/chunk_14_07.blc differ diff --git a/blight-map/src/main/map/chunks/chunk_15_06.blc b/blight-map/src/main/map/chunks/chunk_15_06.blc index 9afc004..b71f657 100644 Binary files a/blight-map/src/main/map/chunks/chunk_15_06.blc and b/blight-map/src/main/map/chunks/chunk_15_06.blc differ diff --git a/blight-map/src/main/map/chunks/chunk_15_07.blc b/blight-map/src/main/map/chunks/chunk_15_07.blc index 2e53ad6..322ddf3 100644 Binary files a/blight-map/src/main/map/chunks/chunk_15_07.blc and b/blight-map/src/main/map/chunks/chunk_15_07.blc differ diff --git a/blight-map/src/main/map/chunks/sculpt_14_0_06.blsm b/blight-map/src/main/map/chunks/sculpt_14_0_06.blsm new file mode 100644 index 0000000..3400717 Binary files /dev/null and b/blight-map/src/main/map/chunks/sculpt_14_0_06.blsm differ diff --git a/blight-map/src/main/map/chunks/sculpt_14_0_07.blsm b/blight-map/src/main/map/chunks/sculpt_14_0_07.blsm new file mode 100644 index 0000000..43c2340 Binary files /dev/null and b/blight-map/src/main/map/chunks/sculpt_14_0_07.blsm differ diff --git a/blight-map/src/main/map/chunks/sculpt_15_0_06.blsm b/blight-map/src/main/map/chunks/sculpt_15_0_06.blsm new file mode 100644 index 0000000..282b78b Binary files /dev/null and b/blight-map/src/main/map/chunks/sculpt_15_0_06.blsm differ diff --git a/blight-map/src/main/map/chunks/sculpt_15_0_07.blsm b/blight-map/src/main/map/chunks/sculpt_15_0_07.blsm new file mode 100644 index 0000000..1250845 Binary files /dev/null and b/blight-map/src/main/map/chunks/sculpt_15_0_07.blsm differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_07.blvc b/blight-map/src/main/map/chunks/voxel_13_0_06.blvc similarity index 82% rename from blight-map/src/main/map/chunks/voxel_14_0_07.blvc rename to blight-map/src/main/map/chunks/voxel_13_0_06.blvc index e1bd7e0..69a6119 100644 Binary files a/blight-map/src/main/map/chunks/voxel_14_0_07.blvc and b/blight-map/src/main/map/chunks/voxel_13_0_06.blvc differ diff --git a/blight-map/src/main/map/chunks/voxel_13_0_07.blvc.prebake b/blight-map/src/main/map/chunks/voxel_13_0_07.blvc similarity index 82% rename from blight-map/src/main/map/chunks/voxel_13_0_07.blvc.prebake rename to blight-map/src/main/map/chunks/voxel_13_0_07.blvc index b39702f..709fc44 100644 Binary files a/blight-map/src/main/map/chunks/voxel_13_0_07.blvc.prebake and b/blight-map/src/main/map/chunks/voxel_13_0_07.blvc differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_06.blvc.prebake b/blight-map/src/main/map/chunks/voxel_14_0_06.blvc.prebake new file mode 100644 index 0000000..0cf630e Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_06.blvc.prebake differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod0.j3o new file mode 100644 index 0000000..240cd19 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod1.j3o new file mode 100644 index 0000000..45bb4c5 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod1.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod2.j3o new file mode 100644 index 0000000..460f2e6 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_06_baked_lod2.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_07.blvc b/blight-map/src/main/map/chunks/voxel_14_0_07.blvc.prebake similarity index 73% rename from blight-map/src/main/map/chunks/voxel_15_0_07.blvc rename to blight-map/src/main/map/chunks/voxel_14_0_07.blvc.prebake index 3655dda..d7a8518 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_07.blvc and b/blight-map/src/main/map/chunks/voxel_14_0_07.blvc.prebake differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod0.j3o new file mode 100644 index 0000000..444c545 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod1.j3o new file mode 100644 index 0000000..e321d27 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod1.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod2.j3o new file mode 100644 index 0000000..71bab81 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_14_0_07_baked_lod2.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc deleted file mode 100644 index 193597c..0000000 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc and /dev/null differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake new file mode 100644 index 0000000..43fce7f Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o new file mode 100644 index 0000000..2ddc896 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o new file mode 100644 index 0000000..df69e0f Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o new file mode 100644 index 0000000..26f091f Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_14_0_06.blvc b/blight-map/src/main/map/chunks/voxel_15_0_07.blvc.prebake similarity index 61% rename from blight-map/src/main/map/chunks/voxel_14_0_06.blvc rename to blight-map/src/main/map/chunks/voxel_15_0_07.blvc.prebake index 23594af..2e8a049 100644 Binary files a/blight-map/src/main/map/chunks/voxel_14_0_06.blvc and b/blight-map/src/main/map/chunks/voxel_15_0_07.blvc.prebake differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod0.j3o new file mode 100644 index 0000000..707273e Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod1.j3o new file mode 100644 index 0000000..6ee4902 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod1.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod2.j3o new file mode 100644 index 0000000..69a2b37 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_15_0_07_baked_lod2.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_13_0_06.blvc.prebake b/blight-map/src/main/map/chunks/voxel_16_0_06.blvc similarity index 79% rename from blight-map/src/main/map/chunks/voxel_13_0_06.blvc.prebake rename to blight-map/src/main/map/chunks/voxel_16_0_06.blvc index af9118b..58962f5 100644 Binary files a/blight-map/src/main/map/chunks/voxel_13_0_06.blvc.prebake and b/blight-map/src/main/map/chunks/voxel_16_0_06.blvc differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07.blvc b/blight-map/src/main/map/chunks/voxel_16_0_07.blvc new file mode 100644 index 0000000..f0b8603 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_16_0_07.blvc differ