procedural-terrains 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FiniteTerrain-B34WMz5S.js → FiniteTerrain-B5_3y6xX.js} +3 -3
- package/dist/{FiniteTerrain-B34WMz5S.js.map → FiniteTerrain-B5_3y6xX.js.map} +1 -1
- package/dist/{InfiniteTerrain-SgoJz3qJ.js → InfiniteTerrain-jeCy6RED.js} +5 -5
- package/dist/{InfiniteTerrain-SgoJz3qJ.js.map → InfiniteTerrain-jeCy6RED.js.map} +1 -1
- package/dist/{MiniTerrain-Blt0H1S0.js → MiniTerrain-Bb5VYkZU.js} +8 -8
- package/dist/MiniTerrain-Bb5VYkZU.js.map +1 -0
- package/dist/{PlanetMaterial-B1_ZPlx5.js → PlanetMaterial-Cs-_x6vr.js} +4 -4
- package/dist/{PlanetMaterial-B1_ZPlx5.js.map → PlanetMaterial-Cs-_x6vr.js.map} +1 -1
- package/dist/{PlanetTerrain-DCvxS1Q8.js → PlanetTerrain-Bsfx7YA6.js} +5 -5
- package/dist/{PlanetTerrain-DCvxS1Q8.js.map → PlanetTerrain-Bsfx7YA6.js.map} +1 -1
- package/dist/{RevisionScope-J__6V9I_.js → RevisionScope-Dl4yjj-0.js} +2 -2
- package/dist/{RevisionScope-J__6V9I_.js.map → RevisionScope-Dl4yjj-0.js.map} +1 -1
- package/dist/{TerrainClouds-BuKvwnWw.js → TerrainClouds-DV6ny7P1.js} +31 -19
- package/dist/TerrainClouds-DV6ny7P1.js.map +1 -0
- package/dist/{TerrainHeightCache-Dsde6EDL.js → TerrainHeightCache-DIZxi9jz.js} +2 -2
- package/dist/{TerrainHeightCache-Dsde6EDL.js.map → TerrainHeightCache-DIZxi9jz.js.map} +1 -1
- package/dist/{TerrainParameters-A3fcNJTR.js → TerrainParameters-DMCjnnxz.js} +10 -8
- package/dist/TerrainParameters-DMCjnnxz.js.map +1 -0
- package/dist/{TerrainWater-Cil7waG3.js → TerrainWater-CX4XdwA0.js} +17 -11
- package/dist/TerrainWater-CX4XdwA0.js.map +1 -0
- package/dist/{WaterMaterial-BUsAp0SS.js → WaterMaterial-C9h1xm9E.js} +7 -5
- package/dist/WaterMaterial-C9h1xm9E.js.map +1 -0
- package/dist/{biomeGLSL-CIkhxIlH.js → biomeGLSL-DWGDDBqT.js} +6 -3
- package/dist/{biomeGLSL-CIkhxIlH.js.map → biomeGLSL-DWGDDBqT.js.map} +1 -1
- package/dist/clouds.js +1 -1
- package/dist/documents.js +6 -6
- package/dist/export.js +2 -2
- package/dist/index.js +1 -1
- package/dist/infinite.js +1 -1
- package/dist/mini.js +1 -1
- package/dist/planet.js +1 -1
- package/dist/{planetGLSL-2xas0f3o.js → planetGLSL-D9Zy2IAw.js} +2 -2
- package/dist/{planetGLSL-2xas0f3o.js.map → planetGLSL-D9Zy2IAw.js.map} +1 -1
- package/dist/real-world.js +1 -1
- package/dist/render.js +1 -1
- package/dist/viewer.js +1 -1
- package/dist/water.js +1 -1
- package/package.json +1 -1
- package/dist/MiniTerrain-Blt0H1S0.js.map +0 -1
- package/dist/TerrainClouds-BuKvwnWw.js.map +0 -1
- package/dist/TerrainParameters-A3fcNJTR.js.map +0 -1
- package/dist/TerrainWater-Cil7waG3.js.map +0 -1
- package/dist/WaterMaterial-BUsAp0SS.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TerrainParameters-DMCjnnxz.js","sources":["../../../src/engine/sky/cloudGLSL.js","../../../src/engine/sky/realistic/realisticCloudGLSL.js","../../../src/engine/sky/CloudShadowPass.js","../../../src/engine/mini/miniCutGLSL.js","../../../src/engine/terrain/TerrainDetailMaterial.js","../../../src/engine/style/ColorPalette.js","../../../src/engine/style/PaletteUniforms.js","../../../src/engine/style/PlanetStyleConfig.js","../../../src/engine/terrain/TerrainMaterial.js","../../../src/runtime/FiniteTerrainConfiguration.js","../../../src/runtime/TerrainParameters.js"],"sourcesContent":["// ============================================================================\n// Cloud-mode GLSL: self-contained 3D procedural noise + a spherical volumetric\n// density field. Deliberately INDEPENDENT of the terrain noise stack — clouds\n// must never be wired into terrain generation, and keeping the noise local\n// means the cloud material does not depend on any terrain uniform/#define.\n//\n// Density is a pure function of a WORLD/PLANET-LOCAL 3D position (the planet is\n// centered at the world origin), never of sphere UVs — so there is no pole\n// stretching and no seam around the globe.\n//\n// Loop bounds are the fixed octave counts, the 3×3×3 worley cell loop and the\n// CLOUD_STEPS / CLOUD_LIGHT_STEPS #defines plus the zero-valued uLoopGuard\n// uniform: identical trip counts, but the D3D compiler keeps real loops instead\n// of unrolling the whole march (the cloud pass ran 2.5x faster, compiled in half\n// the time, pixel-identical). What used to hang ANGLE's D3D11 compiler was\n// implicit-derivative texture lookups inside such loops; the occupancy fetch is\n// an explicit-LOD lookup, and no loop body may sample with derivatives.\n// ============================================================================\n\nexport const CLOUD_NOISE_GLSL = /* glsl */ `\n#ifndef TERRAIN_LOOP_GUARD\n#define TERRAIN_LOOP_GUARD\nuniform int uLoopGuard; // always 0: keeps the loops below as real loops\n#endif\n\n// --- 3D hash (Dave Hoskins) --------------------------------------------------\nfloat cl_hash13(vec3 p3) {\n p3 = fract(p3 * 0.1031);\n p3 += dot(p3, p3.zyx + 31.32);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nvec3 cl_hash33(vec3 p3) {\n p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\n// Stable spatial hash for the experimental chunk march (no temporal crawl).\nfloat cl_ign(vec2 pix) {\n vec3 p = fract(vec3(pix.xyx) * 0.1031);\n p += dot(p, p.yzx + 33.33);\n return fract((p.x + p.y) * p.z);\n}\n\n// --- quintic trilinear value noise -------------------------------------------\nfloat cl_vnoise(vec3 p) {\n vec3 i = floor(p);\n vec3 f = fract(p);\n vec3 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n float n000 = cl_hash13(i + vec3(0.0, 0.0, 0.0));\n float n100 = cl_hash13(i + vec3(1.0, 0.0, 0.0));\n float n010 = cl_hash13(i + vec3(0.0, 1.0, 0.0));\n float n110 = cl_hash13(i + vec3(1.0, 1.0, 0.0));\n float n001 = cl_hash13(i + vec3(0.0, 0.0, 1.0));\n float n101 = cl_hash13(i + vec3(1.0, 0.0, 1.0));\n float n011 = cl_hash13(i + vec3(0.0, 1.0, 1.0));\n float n111 = cl_hash13(i + vec3(1.0, 1.0, 1.0));\n return mix(\n mix(mix(n000, n100, u.x), mix(n010, n110, u.x), u.y),\n mix(mix(n001, n101, u.x), mix(n011, n111, u.x), u.y),\n u.z\n );\n}\n\n// orthonormal rotation to decorrelate FBM octaves\nconst mat3 CL_ROT = mat3(\n 0.00, 0.80, 0.60,\n -0.80, 0.36, -0.48,\n -0.60, -0.48, 0.64\n);\n\n// Base FBM value noise\nfloat cl_fbm_base(vec3 p) {\n float amp = 0.5, sum = 0.0, norm = 0.0;\n #ifndef CLOUD_OCTAVES\n #define CLOUD_OCTAVES 5\n #endif\n for (int i = 0; i < CLOUD_OCTAVES + uLoopGuard; i++) {\n sum += amp * cl_vnoise(p);\n norm += amp;\n amp *= 0.5;\n p = CL_ROT * p * 2.02;\n }\n return sum / max(norm, 1e-4);\n}\n\n// Lighting probes do not affect the visible silhouette. Keep them deliberately\n// smooth and cheap: three base octaves, no detail FBM, and no Worley erosion.\nfloat cl_fbm_light(vec3 p) {\n float amp = 0.5, sum = 0.0, norm = 0.0;\n for (int i = 0; i < 3 + uLoopGuard; i++) {\n sum += amp * cl_vnoise(p);\n norm += amp;\n amp *= 0.5;\n p = CL_ROT * p * 2.02;\n }\n return sum / max(norm, 1e-4);\n}\n\n// Detail FBM value noise (compiled out if detail octaves is 0)\n#if defined(CLOUD_DETAIL_OCTAVES) && CLOUD_DETAIL_OCTAVES > 0\nfloat cl_fbm_detail(vec3 p) {\n float amp = 0.5, sum = 0.0, norm = 0.0;\n for (int i = 0; i < CLOUD_DETAIL_OCTAVES + uLoopGuard; i++) {\n sum += amp * cl_vnoise(p);\n norm += amp;\n amp *= 0.5;\n p = CL_ROT * p * 2.02;\n }\n return sum / max(norm, 1e-4);\n}\n#endif\n\n// Worley / cellular noise (F1) over a fixed 3×3×3 neighbourhood — returns the\n// distance to the nearest feature point. Used to erode wispy cloud edges.\nfloat cl_worley(vec3 p) {\n vec3 id = floor(p);\n vec3 f = fract(p);\n float md = 1.0;\n for (int z = -1; z <= 1 + uLoopGuard; z++)\n for (int y = -1; y <= 1 + uLoopGuard; y++)\n for (int x = -1; x <= 1 + uLoopGuard; x++) {\n vec3 g = vec3(float(x), float(y), float(z));\n vec3 o = cl_hash33(id + g);\n vec3 r = g + o - f;\n md = min(md, dot(r, r));\n }\n return sqrt(md);\n}\n\nuniform float uCloudNoiseType; // 0 = soft, 1 = billowy, 2 = wispy\n\nvec3 cl_formationPoint(vec3 p) {\n return uCloudNoiseType > 1.5 ? p * vec3(0.55, 1.7, 1.4) : p;\n}\n\nfloat cl_formationBase(vec3 p, float base) {\n float result = base;\n if (uCloudNoiseType > 1.5) {\n float fibers = cl_vnoise(p * vec3(0.65, 2.4, 1.6) + 19.7);\n // Keep coverage centred on the common field; wisps stay thin through\n // their vertical profile and opacity rather than losing the whole layer.\n result = base + 0.04 - 0.08 * smoothstep(0.25, 0.75, fibers);\n } else if (uCloudNoiseType > 0.5) {\n // One initialized return value avoids ANGLE/FXC's branch-return warning.\n float billows = 0.67 - abs(cl_vnoise(p * 1.5 + 13.7) - 0.5) * 1.4;\n result = mix(base, billows, 0.65);\n }\n return result;\n}\n`;\n\n// Shared cloud field: the uniforms + domain rotation + shape function reused by\n// BOTH the spherical shell (planet) and the planar slab (studio) shaders. Only\n// the altitude falloff and the marched geometry differ between them.\nexport const CLOUD_FIELD_GLSL = /* glsl */ `\nuniform float uCloudCoverage; // 0..1, higher = more cloud\nuniform float uCloudSoftness;\nuniform float uCloudScale; // pre-scaled large-shape frequency\nuniform float uCloudDetailScale;\nuniform float uCloudDetailStrength;\nuniform float uCloudErosionScale;\nuniform float uCloudErosionStrength;\nuniform float uCloudExtinction; // optical-depth gain (folds in cloudDensity)\nuniform float uCloudLightAbsorption;\nuniform float uCloudShadowStrength;\nuniform float uCloudScattering;\nuniform vec3 uCloudColor;\nuniform vec3 uCloudShadowColor;\nuniform vec3 uCloudDirectLight;\nuniform vec3 uCloudAmbientTop;\nuniform vec3 uCloudAmbientBottom;\nuniform vec3 uCloudGroundBounce;\nuniform float uCloudAtmosphereInfluence;\nuniform float uCloudSunResponse;\nuniform float uCloudAmbientResponse;\nuniform float uCloudSilverLining;\nuniform vec3 uCloudWind; // domain drift vector (already × speed)\nuniform vec3 uCloudNoiseOffset; // accumulated noise-space offset for rebasing\nuniform vec3 uCloudVariationOffset;\nuniform vec3 uCloudDomainOrigin; // world-space origin subtracted before domain rotation\nuniform float uCloudRotation; // domain rotation angle (radians)\nuniform float uCloudTime;\nuniform float uCloudSelfShadow; // 0/1 toggle\nuniform vec3 uCloudSunDir; // normalized, surface -> sun\nuniform float uCloudStepScale; // 0.4..1 — distance LOD on the primary march\nuniform float uCloudEvolve; // noise-space units/sec: scroll through the\n // field so clouds FORM / MORPH / DISSIPATE in\n // place instead of only translating (wind).\n\n// rotate the sample domain slowly around the up (Y) axis (seamless — no UVs)\nvec3 cl_domain(vec3 P) {\n float c = cos(uCloudRotation), s = sin(uCloudRotation);\n return vec3(c * P.x + s * P.z, P.y, -s * P.x + c * P.z);\n}\n\n// cloud coverage fraction in [0,1] from the 3D noise stack at a domain point\n// (BEFORE altitude falloff — each shader applies its own).\nfloat cloudShape(vec3 q) {\n vec3 drift = uCloudWind * uCloudTime;\n // Evolution: scroll the sample point along a 3rd noise axis over time. Because\n // the field is genuinely 3D, moving the sampled slice makes the 2D cloud\n // pattern continuously re-form and dissipate (NOT just slide like wind does).\n // The falloff in cloudDensity uses the REAL altitude, so this only morphs the\n // shape — it never moves the shell.\n float evoT = uCloudTime * uCloudEvolve;\n vec3 baseP = q * uCloudScale + uCloudNoiseOffset + uCloudVariationOffset\n + drift + vec3(0.0, evoT, 0.0);\n baseP = cl_formationPoint(baseP);\n // coverage: higher slider -> lower threshold -> more cloud\n float threshold = 1.0 - uCloudCoverage;\n float soft = max(uCloudSoftness, 0.06);\n float base = cl_formationBase(baseP, cl_fbm_base(baseP));\n\n // Soft base coverage from the LARGE-scale shape first. This is the smooth\n // volume; detail/erosion below are folded in only across the transition band\n // so raw high-frequency noise never maps straight to opacity (that direct\n // mapping was the source of the salt-and-pepper grain, and it compounded when\n // a ray crossed several cloud masses).\n float cov = smoothstep(threshold, threshold + soft, base);\n if (cov <= 0.0) return 0.0;\n\n // Edge mask: ~1 across the soft transition band, ~0 in solid cores and in\n // clear sky (cov*(1-cov) peaks at cov=0.5). Detail and erosion act ONLY here,\n // so cloud cores stay smooth, clear sky stays empty, and the wisps live on the\n // edges where they read as shape variation rather than visible pixels.\n float edge = 4.0 * cov * (1.0 - cov);\n\n // Smaller features churn faster than the large masses → \"boiling\" edges. The\n // detail/erosion octaves get their own, quicker evolution offset.\n float evoD = uCloudTime * uCloudEvolve * 2.5;\n float carve = 0.0;\n // carve only acts through edge, which is exactly 0 in solid cores\n // (cov == 1): skip the detail FBM and the 27-cell Worley there.\n if (edge > 0.0) {\n #if defined(CLOUD_DETAIL_OCTAVES) && CLOUD_DETAIL_OCTAVES > 0\n // centered (detail - 0.5): redistributes density instead of biasing brightness\n float detailRatio = uCloudDetailScale / max(uCloudScale, 1e-6);\n float detail = cl_fbm_detail(q * uCloudDetailScale\n + (uCloudNoiseOffset + uCloudVariationOffset) * detailRatio\n + drift * 1.7 + vec3(0.0, evoD, 0.0));\n carve += (detail - 0.5) * uCloudDetailStrength;\n #endif\n #if defined(CLOUD_USE_EROSION) && CLOUD_USE_EROSION > 0\n float erosionRatio = uCloudErosionScale / max(uCloudScale, 1e-6);\n float ero = cl_worley(q * uCloudErosionScale\n + (uCloudNoiseOffset + uCloudVariationOffset) * erosionRatio\n + vec3(0.0, evoD, 0.0));\n carve -= ero * uCloudErosionStrength;\n #endif\n }\n\n float dens = clamp(cov + carve * edge, 0.0, 1.0);\n // final soft ease so density ramps in gently — never a binary threshold\n return dens * dens * (3.0 - 2.0 * dens) * (uCloudNoiseType > 1.5 ? 0.62 : 1.0);\n}\n\nfloat cloudShapeForLight(vec3 q) {\n vec3 drift = uCloudWind * uCloudTime;\n float evoT = uCloudTime * uCloudEvolve;\n vec3 baseP = q * uCloudScale + uCloudNoiseOffset + uCloudVariationOffset\n + drift + vec3(0.0, evoT, 0.0);\n baseP = cl_formationPoint(baseP);\n float threshold = 1.0 - uCloudCoverage;\n float soft = max(uCloudSoftness, 0.08);\n float base = cl_formationBase(baseP, cl_fbm_light(baseP));\n float cov = smoothstep(threshold, threshold + soft, base);\n return cov * cov * (3.0 - 2.0 * cov) * (uCloudNoiseType > 1.5 ? 0.62 : 1.0);\n}\n`;\n\n// Spherical shell specifics (planet mode).\nexport const CLOUD_VOLUME_GLSL = /* glsl */ `\nuniform float uCloudInner; // inner shell radius (world units)\nuniform float uCloudOuter; // outer shell radius (world units)\n\n// Cloud fraction + normalized layer height at a planet-local world position.\n// Returning both avoids a second length(P) in the primary lighting loop.\nvec2 cloudSample(vec3 P) {\n float r = length(P);\n float hf = (r - uCloudInner) / max(uCloudOuter - uCloudInner, 1e-3);\n if (hf <= 0.0 || hf >= 1.0) return vec2(0.0, clamp(hf, 0.0, 1.0));\n float fall = smoothstep(0.0, 0.18, hf) * smoothstep(1.0, 0.78, hf);\n if (uCloudNoiseType > 1.5) fall = smoothstep(0.0, 0.12, hf) * (1.0 - smoothstep(0.30, 0.62, hf));\n return vec2(cloudShape(cl_domain(P)) * fall, hf);\n}\n\nfloat cloudDensity(vec3 P) {\n return cloudSample(P).x;\n}\n\nfloat cl_sunVisibility(vec3 P, float cloudHeight) {\n float r = mix(uCloudInner, uCloudOuter, clamp(cloudHeight, 0.0, 1.0));\n float localSun = dot(P, uCloudSunDir) / max(r, 1e-3);\n return smoothstep(-0.08, 0.05, localSun);\n}\n\nfloat cloudDensityForLight(vec3 P) {\n float r = length(P);\n float hf = (r - uCloudInner) / max(uCloudOuter - uCloudInner, 1e-3);\n if (hf <= 0.0 || hf >= 1.0) return 0.0;\n float fall = smoothstep(0.0, 0.18, hf) * smoothstep(1.0, 0.78, hf);\n if (uCloudNoiseType > 1.5) fall = smoothstep(0.0, 0.12, hf) * (1.0 - smoothstep(0.30, 0.62, hf));\n return cloudShapeForLight(cl_domain(P)) * fall;\n}\n\n// ray vs sphere centered at the origin; returns (tNear, tFar), tNear > tFar\n// means no intersection. ro/rd in planet-local world space, rd normalized.\nvec2 cl_raySphere(vec3 ro, vec3 rd, float R) {\n float b = dot(ro, rd);\n float c = dot(ro, ro) - R * R;\n float disc = b * b - c;\n if (disc < 0.0) return vec2(1.0, -1.0);\n float s = sqrt(disc);\n return vec2(-b - s, -b + s);\n}\n\n// soft secondary lighting toward the sun for self-shadowing\nfloat cl_lightTransmittance(vec3 P) {\n float span = uCloudOuter - uCloudInner;\n#if defined(CLOUD_LIGHT_MODE) && CLOUD_LIGHT_MODE == 1\n // cheap 2-tap analytic shadow: probe density toward the sun at two fixed\n // offsets and fold into a single Beer term (no secondary march loop). The\n // 0.65 span factor matches the marched path's effective optical depth so\n // cloud brightness stays consistent with the full-march mode.\n float d0 = cloudDensityForLight(P + uCloudSunDir * span * 0.12);\n float d1 = cloudDensityForLight(P + uCloudSunDir * span * 0.40);\n float dsum = d0 * 0.65 + d1 * 0.35;\n return exp(-dsum * span * 0.65 * uCloudExtinction * uCloudLightAbsorption);\n#else\n float stepLen = span / float(CLOUD_LIGHT_STEPS) * 0.65;\n float dsum = 0.0;\n vec3 sp = P;\n for (int i = 0; i < CLOUD_LIGHT_STEPS + uLoopGuard; i++) {\n sp += uCloudSunDir * stepLen;\n dsum += cloudDensityForLight(sp);\n }\n return exp(-dsum * stepLen * uCloudExtinction * uCloudLightAbsorption);\n#endif\n}\n`;\n\n// Planar slab specifics (studio / flat board mode). Clouds live between two\n// horizontal planes (uCloudBottom..uCloudTop) and fade out past a horizontal\n// radius so they sit over the board like a diorama layer.\nexport const CLOUD_SLAB_GLSL = /* glsl */ `\nuniform float uCloudBottom; // slab bottom world Y\nuniform float uCloudTop; // slab top world Y\nuniform float uCloudRadius; // horizontal fade radius\nuniform float uCloudFar; // clamp marched distance (horizon bound)\nuniform vec3 uCloudCenter; // board center (xz used)\n\nvec2 cloudSample(vec3 P) {\n float hf = (P.y - uCloudBottom) / max(uCloudTop - uCloudBottom, 1e-3);\n if (hf <= 0.0 || hf >= 1.0) return vec2(0.0, clamp(hf, 0.0, 1.0));\n float fall = smoothstep(0.0, 0.18, hf) * smoothstep(1.0, 0.78, hf);\n if (uCloudNoiseType > 1.5) fall = smoothstep(0.0, 0.12, hf) * (1.0 - smoothstep(0.30, 0.62, hf));\n float rad = length(P.xz - uCloudCenter.xz);\n float edge = 1.0 - smoothstep(uCloudRadius * 0.65, uCloudRadius, rad);\n if (edge <= 0.0) return vec2(0.0, hf);\n return vec2(cloudShape(cl_domain(P - uCloudDomainOrigin)) * fall * edge, hf);\n}\nfloat cloudDensity(vec3 P) {\n return cloudSample(P).x;\n}\nfloat cl_sunVisibility(vec3 P, float cloudHeight) {\n return smoothstep(-0.08, 0.08, uCloudSunDir.y);\n}\nfloat cloudDensityForLight(vec3 P) {\n float hf = (P.y - uCloudBottom) / max(uCloudTop - uCloudBottom, 1e-3);\n if (hf <= 0.0 || hf >= 1.0) return 0.0;\n float fall = smoothstep(0.0, 0.18, hf) * smoothstep(1.0, 0.78, hf);\n if (uCloudNoiseType > 1.5) fall = smoothstep(0.0, 0.12, hf) * (1.0 - smoothstep(0.30, 0.62, hf));\n float rad = length(P.xz - uCloudCenter.xz);\n float edge = 1.0 - smoothstep(uCloudRadius * 0.65, uCloudRadius, rad);\n return cloudShapeForLight(cl_domain(P - uCloudDomainOrigin)) * fall * max(edge, 0.0);\n}\n\n\n\nfloat cl_lightTransmittance(vec3 P) {\n float span = uCloudTop - uCloudBottom;\n#if defined(CLOUD_LIGHT_MODE) && CLOUD_LIGHT_MODE == 1\n // cheap 2-tap analytic shadow (see the spherical shader for rationale)\n float d0 = cloudDensityForLight(P + uCloudSunDir * span * 0.12);\n float d1 = cloudDensityForLight(P + uCloudSunDir * span * 0.40);\n float dsum = d0 * 0.65 + d1 * 0.35;\n return exp(-dsum * span * 0.65 * uCloudExtinction * uCloudLightAbsorption);\n#else\n float stepLen = span / float(CLOUD_LIGHT_STEPS) * 0.65;\n float dsum = 0.0;\n vec3 sp = P;\n for (int i = 0; i < CLOUD_LIGHT_STEPS + uLoopGuard; i++) {\n sp += uCloudSunDir * stepLen;\n dsum += cloudDensityForLight(sp);\n }\n return exp(-dsum * stepLen * uCloudExtinction * uCloudLightAbsorption);\n#endif\n}\n`;\n","// ============================================================================\n// Realistic cloud mode — GLSL. A port of the Procedural Planets cloud pass\n// (PlanetPipeline CLOUD_FRAGMENT + surfaceGLSL CLOUD_FIELD_GLSL): a baked\n// weather field (cover + cloud type) shapes columns whose vertical profile\n// depends on the cloud type, tileable Perlin-Worley / Worley volumes carve the\n// billows and the erosion, and the lighting is single scattering with\n// multiple-scattering octaves (Wrenninge), powder darkening, silver lining,\n// sky light and ground bounce.\n//\n// Two geometries share the march:\n// CLOUD_PLANET spherical shell around the origin, weather cubemap\n// (default) flat slab between two world-Y planes, weather 2D map over a\n// world-XZ window that follows the wind drift\n//\n// The loops are bounded by constants + the zero-valued uLoopGuard (real loops\n// on ANGLE/D3D, see cloudGLSL.js); every texture fetch in a loop is explicit\n// LOD.\n// ============================================================================\n\nexport const REALISTIC_MAX_STEPS = 128;\n\nconst COMMON_GLSL = /* glsl */ `\n#ifndef PI\n#define PI 3.141592653589793\n#endif\n\nfloat sat(float x) { return clamp(x, 0.0, 1.0); }\nvec3 srgbToLinear(vec3 c) { return pow(max(c, vec3(0.0)), vec3(2.2)); }\n\nfloat remap(float v, float a, float b, float c, float d) {\n return c + (v - a) / max(b - a, 1e-5) * (d - c);\n}\n\nfloat phaseHG(float mu, float g) {\n float g2 = g * g;\n return (1.0 - g2) / (4.0 * PI * pow(max(1.0 + g2 - 2.0 * g * mu, 1e-4), 1.5));\n}\n\nvec3 aces(vec3 x) {\n const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;\n return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);\n}\n\nvec3 linearToSrgb(vec3 c) {\n return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(0.0031308, c));\n}\n\n// ray / sphere at the origin: (tNear, tFar); tNear > tFar means miss.\n// Factored radial difference keeps metre-scale clearance on large planets.\nvec2 raySphere(vec3 ro, vec3 rd, float r) {\n float b = dot(ro, rd);\n float eyeRadius = length(ro);\n float c = (eyeRadius - r) * (eyeRadius + r);\n float h = b * b - c;\n if (h < 0.0) return vec2(1e20, -1e20);\n h = sqrt(h);\n float q = -b - (b < 0.0 ? -h : h);\n float other = abs(q) > 1e-20 ? c / q : -b;\n return vec2(min(q, other), max(q, other));\n}\n`;\n\n// ---------------------------------------------------------------------------\n// Bake: tileable 3D noise volumes (one layer per draw), as in Procedural\n// Planets. r = Worley fbm (4 / 8 / 16 cells), g = Perlin-Worley.\n// ---------------------------------------------------------------------------\nexport const NOISE_VOLUME_FRAGMENT = /* glsl */ `\nprecision highp float;\nuniform float uLayer;\nuniform float uSize;\n\nvec3 hash33(vec3 p3) {\n p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx);\n}\n\nfloat worleyTile(vec3 p, float F) {\n vec3 q = p * F;\n vec3 i = floor(q);\n vec3 f = fract(q);\n float d = 1.0;\n for (int x = -1; x <= 1; x++)\n for (int y = -1; y <= 1; y++)\n for (int z = -1; z <= 1; z++) {\n vec3 g = vec3(float(x), float(y), float(z));\n vec3 o = hash33(mod(i + g, F) + 13.7);\n vec3 r = g + o - f;\n d = min(d, dot(r, r));\n }\n return 1.0 - clamp(sqrt(d), 0.0, 1.0);\n}\n\nfloat gradTile(vec3 p, float F) {\n vec3 q = p * F;\n vec3 i = floor(q);\n vec3 f = fract(q);\n vec3 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n #define G(c) dot(hash33(mod(i + c, F)) * 2.0 - 1.0, f - c)\n float v000 = G(vec3(0.0, 0.0, 0.0)), v100 = G(vec3(1.0, 0.0, 0.0));\n float v010 = G(vec3(0.0, 1.0, 0.0)), v110 = G(vec3(1.0, 1.0, 0.0));\n float v001 = G(vec3(0.0, 0.0, 1.0)), v101 = G(vec3(1.0, 0.0, 1.0));\n float v011 = G(vec3(0.0, 1.0, 1.0)), v111 = G(vec3(1.0, 1.0, 1.0));\n #undef G\n return mix(mix(mix(v000, v100, u.x), mix(v010, v110, u.x), u.y),\n mix(mix(v001, v101, u.x), mix(v011, v111, u.x), u.y), u.z);\n}\n\nfloat worleyFbm(vec3 p, float F) {\n return worleyTile(p, F) * 0.625 + worleyTile(p, F * 2.0) * 0.25 + worleyTile(p, F * 4.0) * 0.125;\n}\n\nvoid main() {\n vec3 p = vec3(gl_FragCoord.xy, uLayer + 0.5) / uSize;\n float perlin = gradTile(p, 4.0) + 0.5 * gradTile(p, 8.0) + 0.25 * gradTile(p, 16.0);\n perlin = clamp(0.5 + perlin * 0.75, 0.0, 1.0);\n float w1 = worleyFbm(p, 4.0);\n // Perlin dilated by Worley: billowy cells with connected bodies\n float pw = clamp((perlin - (w1 - 1.0)) / (2.0 - w1), 0.0, 1.0);\n float low = w1 * 0.625 + worleyFbm(p, 8.0) * 0.25 + worleyFbm(p, 16.0) * 0.125;\n gl_FragColor = vec4(low, pw, 0.0, 1.0);\n}\n`;\n\n// ---------------------------------------------------------------------------\n// Bake: weather field (r = cloud field, g = cloud type). Same warped fbm,\n// frontal bands and cloud-type noise as Procedural Planets; the planet version\n// keeps the Hadley / Ferrel latitude cells, the flat one evolves through Y.\n// ---------------------------------------------------------------------------\nconst WEATHER_NOISE_GLSL = /* glsl */ `\nprecision highp float;\nuniform vec3 uSeedOffset;\nuniform float uWeatherTime;\nuniform float uFaceSize;\nuniform int uLoopGuard;\n\nvec3 hash33(vec3 p3) {\n p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));\n p3 += dot(p3, p3.yxz + 33.33);\n return fract((p3.xxy + p3.yxx) * p3.zyx) * 2.0 - 1.0;\n}\n\nfloat gnoise(vec3 x) {\n vec3 i = floor(x);\n vec3 f = fract(x);\n vec3 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n float va = dot(hash33(i), f);\n float vb = dot(hash33(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0));\n float vc = dot(hash33(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0));\n float vd = dot(hash33(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0));\n float ve = dot(hash33(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0));\n float vf = dot(hash33(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0));\n float vg = dot(hash33(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0));\n float vh = dot(hash33(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0));\n return mix(mix(mix(va, vb, u.x), mix(vc, vd, u.x), u.y),\n mix(mix(ve, vf, u.x), mix(vg, vh, u.x), u.y), u.z);\n}\n\nconst mat3 OCT_ROT = mat3( 0.00, 0.80, 0.60,\n -0.80, 0.36, -0.48,\n -0.60, -0.48, 0.64);\n\nfloat fbmN(vec3 p, int oct) {\n float s = 0.0, a = 0.5, n = 0.0;\n for (int i = 0; i < 6 + uLoopGuard; i++) {\n if (i >= oct) break;\n s += a * gnoise(p);\n n += a;\n a *= 0.52;\n p = OCT_ROT * p * 2.03;\n }\n return s / n;\n}\n\n// warped field + frontal bands; t = weather time, e = evolution axis\nfloat weatherField(vec3 p, float t, vec3 e) {\n vec3 q = vec3(fbmN(p * 0.55 + e * t, 3),\n fbmN(p * 0.55 + vec3(5.2, 1.3, 0.0) - e * t, 3),\n fbmN(p * 0.55 + vec3(2.8, 7.7, 0.0) + e * (t * 0.7), 3));\n vec3 pw = p + q * 3.2;\n float f = fbmN(pw + e * (t * 0.3), 6);\n float fb = 1.0 - abs(fbmN(pw * 0.7 + vec3(19.1, 7.3, 3.9) - e * (t * 0.2), 5)) * 5.5;\n return 0.47 + f * 1.6 + max(fb, 0.0) * 0.2;\n}\n`;\n\nexport const WEATHER_CUBE_FRAGMENT = /* glsl */ `\n${WEATHER_NOISE_GLSL}\nuniform float uFace;\nuniform float uCloudScale;\n\nvec3 faceDir(float face, vec2 st) {\n if (face < 0.5) return vec3(1.0, -st.y, -st.x);\n if (face < 1.5) return vec3(-1.0, -st.y, st.x);\n if (face < 2.5) return vec3(st.x, 1.0, st.y);\n if (face < 3.5) return vec3(st.x, -1.0, -st.y);\n if (face < 4.5) return vec3(st.x, -st.y, 1.0);\n return vec3(-st.x, -st.y, -1.0);\n}\n\nvoid main() {\n vec2 st = gl_FragCoord.xy / uFaceSize * 2.0 - 1.0;\n vec3 d = normalize(faceDir(uFace, st));\n // systems are stretched east-west (zonal flow)\n vec3 p = d * uCloudScale * vec3(1.0, 1.7, 1.0) + uSeedOffset * 0.37;\n float v = weatherField(p, uWeatherTime, vec3(0.0, 0.0, 1.0));\n // Hadley / Ferrel circulation: cloudy ITCZ and storm tracks, clear highs\n float lat = asin(clamp(d.y, -1.0, 1.0));\n v += cos(lat * 6.0) * 0.05;\n // cloud type: tall convective cells (tropics) vs flat stratiform decks\n float type = clamp(0.45 + gnoise(p * 1.6 + 31.0) * 1.3 + (1.0 - abs(d.y)) * 0.25 - 0.12, 0.0, 1.0);\n gl_FragColor = vec4(clamp(v, 0.0, 1.0), type, 0.0, 1.0);\n}\n`;\n\nexport const WEATHER_FLAT_FRAGMENT = /* glsl */ `\n${WEATHER_NOISE_GLSL}\nuniform vec2 uWindowCenter; // field-space centre of the baked window\nuniform float uWindowHalf; // half width (world units)\nuniform float uCloudScale; // weather frequency per world unit\n\nvoid main() {\n vec2 st = gl_FragCoord.xy / uFaceSize * 2.0 - 1.0;\n vec2 F = uWindowCenter + st * uWindowHalf;\n vec3 p = vec3(F.x, 0.0, F.y) * uCloudScale + uSeedOffset * 0.37;\n // the field evolves through the axis normal to the plane\n float v = weatherField(p, uWeatherTime, vec3(0.0, 1.0, 0.0));\n float type = clamp(0.45 + gnoise(p * 1.6 + 31.0) * 1.3 + 0.01, 0.0, 1.0);\n gl_FragColor = vec4(clamp(v, 0.0, 1.0), type, 0.0, 1.0);\n}\n`;\n\n// ---------------------------------------------------------------------------\n// The volumetric pass, drawn on the layer's bounding mesh (BackSide).\n// ---------------------------------------------------------------------------\nexport const REALISTIC_CLOUD_VERTEX = /* glsl */ `\nvarying vec3 vWorldPos;\nvoid main() {\n vec4 wp = modelMatrix * vec4(position, 1.0);\n vWorldPos = wp.xyz;\n gl_Position = projectionMatrix * viewMatrix * wp;\n}\n`;\n\nexport const REALISTIC_CLOUD_FIELD_GLSL = /* glsl */ `\nprecision highp float;\nprecision highp sampler3D;\n${COMMON_GLSL}\n\nvarying vec3 vWorldPos;\n\nuniform int uLoopGuard;\nuniform highp sampler3D uCloudNoise; // Worley fbm (r) + Perlin-Worley (g)\nuniform highp sampler3D uCloudErosion; // Worley fbm alone (detail taps)\nuniform float uWeatherBlend;\nuniform float uCloudShapeFreq;\nuniform float uCloudDetailFreq;\nuniform float uCloudCellFreq;\nuniform vec3 uCloudWind;\nuniform vec3 uCloudVariationOffset;\nuniform float uCloudNoiseType; // 0 = soft, 1 = billowy, 2 = wispy\nuniform float uCloudCoverage;\nuniform float uCloudSoftness;\nuniform float uCloudDensity;\nuniform float uCloudDetail;\nuniform float uCloudTowering;\nuniform float uCloudShear;\nuniform float uCloudBottom;\nuniform float uCloudTop;\nuniform float uCloudSilver;\nuniform vec3 uCloudColor; // sRGB albedo tint\nuniform vec3 uCloudSkyTint; // sRGB tint of the sky-lit (ambient) part\nuniform float uCloudSteps;\nuniform float uExposure;\n\nuniform vec3 uSunDir;\nuniform vec3 uSunLight; // linear sun irradiance (HDR)\nuniform vec3 uSkyTop; // linear sky irradiance above / below the layer\nuniform vec3 uSkyBottom;\n\nuniform sampler2D tSceneDepth;\nuniform vec2 uDepthResolution;\nuniform mat4 uProjectionMatrixInverse;\nuniform mat4 uViewMatrixInverse;\nuniform float uDepthBias;\nuniform float uUseDepth;\n\n#ifdef CLOUD_PLANET\nuniform samplerCube uWeatherMap;\nuniform samplerCube uWeatherMapNext;\nuniform vec2 uCloudRotCS; // drift rotation around the pole (cos, sin)\nuniform float uGroundRadius; // sea level radius\n#else\nuniform sampler2D uWeatherMap;\nuniform sampler2D uWeatherMapNext;\nuniform vec2 uFieldShift; // accumulated wind drift: field = world - shift\nuniform vec2 uWeatherCenter; // field-space centre of the baked window\nuniform float uWeatherInvSpan; // 1 / window width\nuniform float uWeatherSize; // weather map texels per side\nuniform vec2 uShearDir; // downwind direction (XZ)\nuniform vec2 uCloudCenter; // XZ centre of the cloud disc\nuniform float uCloudRadius;\nuniform float uCloudFar;\n#endif\n\n// 0..1 cloud cover from the weather field and the coverage slider\nfloat cloudCover(vec4 w) {\n float cut = 1.0 - uCloudCoverage;\n float soft = max(uCloudSoftness, 0.01);\n return smoothstep(cut - soft * 0.15, cut + soft + 0.05, w.r);\n}\n\n// Vertical extent of the cloud types as fractions of the layer: x / y base\n// ramp, z / w top ramp. Stratiform decks stay low and flat, cumulus are as\n// tall as wide, cumulonimbus fill the layer; dense cores build higher than\n// their fringes, which shapes domes.\nvec4 cloudGradient(float type, float cov) {\n float tt = sat(type * (0.4 + 1.2 * uCloudTowering));\n float top = mix(0.3, 1.0, sat(tt * 0.75 + cov * 0.45));\n if (uCloudNoiseType > 1.5) top *= 0.6;\n else if (uCloudNoiseType > 0.5) top = mix(top, 1.0, 0.25);\n return vec4(0.0, 0.07, top * mix(0.3, 0.15, tt), top);\n}\n\n// per-cell tower heights (w.w = column noise)\nvec4 cloudColumnGradient(vec4 w) {\n vec4 g = cloudGradient(w.y, w.x);\n float tt = sat(w.y * (0.4 + 1.2 * uCloudTowering));\n g.w = min(g.w * mix(1.0, mix(0.62, 1.18, w.w), 0.3 + 0.7 * tt), 1.0);\n g.z = min(g.z, g.w * 0.8);\n return g;\n}\n\nfloat cloudProfile(float hf, vec4 g) {\n return smoothstep(g.x, g.y, hf) * (1.0 - smoothstep(g.z, g.w, hf));\n}\n\n#ifdef CLOUD_PLANET\nvec3 cloudRotate(vec3 d) {\n return vec3(d.x * uCloudRotCS.x - d.z * uCloudRotCS.y, d.y, d.x * uCloudRotCS.y + d.z * uCloudRotCS.x);\n}\n\n// Zonal wind shear: the upper part of a cloud is carried downstream, so\n// towers lean. Trade-wind easterlies in the tropics, westerlies at mid\n// latitudes.\nvec3 shearOffset(vec3 d, float hf) {\n float lat = abs(d.y);\n float s = mix(-1.0, 1.0, smoothstep(0.42, 0.58, lat)) * (1.0 - smoothstep(0.85, 0.97, lat));\n return vec3(-d.z, 0.0, d.x) * (s * uCloudShear * hf);\n}\n\nvec4 weatherRotated(vec3 dr) {\n return mix(textureLod(uWeatherMap, dr, 0.0), textureLod(uWeatherMapNext, dr, 0.0), uWeatherBlend);\n}\n\nvec3 cloudUp(vec3 p) { return normalize(p); }\n\nfloat heightFrac(vec3 p) {\n return (length(p) - uCloudBottom) / (uCloudTop - uCloudBottom);\n}\n\n// weather at p: x cover, y type, z unused, w column noise (-1 = not fetched)\nvec4 cloudWeather(vec3 p, float hf) {\n vec3 d = normalize(p);\n vec4 w = weatherRotated(cloudRotate(d + shearOffset(d, hf)));\n return vec4(cloudCover(w), w.g, 0.0, -1.0);\n}\n\nvec4 cloudWeatherLight(vec3 p, float hf) {\n vec4 w = cloudWeather(p, hf);\n w.w = 0.5;\n return w;\n}\n\n// position of the billow noise (the billows ride a little of the shear)\nvec3 billowPos(vec3 p, float hf) {\n float r = length(p);\n vec3 d = p / r;\n return cloudRotate(d + shearOffset(d, 0.3) * hf) * r;\n}\n\n// per-column tower height noise: sampled at the base radius so it is\n// constant up a column\nfloat columnNoise(vec3 p, float hf) {\n vec3 d = normalize(p);\n return textureLod(uCloudNoise, cloudRotate(d + shearOffset(d, hf)) * uCloudBottom * uCloudCellFreq + uCloudWind * 0.2 + uCloudVariationOffset, 0.0).r;\n}\n\n// direct sun reaching p: planet shadow with a soft penumbra and reddening\n// toward the terminator (stands in for the transmittance LUT)\nvec3 sunIrradiance(vec3 p) {\n float r = length(p);\n float mu = dot(p / r, uSunDir);\n float muH = -sqrt(max(1.0 - (uGroundRadius * uGroundRadius) / (r * r), 0.0));\n float lit = smoothstep(muH - 0.035, muH + 0.012, mu);\n float air = min(1.0 / max(mu - muH + 0.02, 0.02), 25.0);\n return uSunLight * lit * exp(-vec3(0.06, 0.14, 0.32) * air * 0.35);\n}\n\nvec3 skyIrradiance(vec3 p, float hf) {\n float mu = dot(normalize(p), uSunDir);\n float day = smoothstep(-0.22, 0.35, mu);\n return mix(uSkyBottom, uSkyTop, sat(hf)) * day + vec3(0.004, 0.005, 0.008) * 6.0;\n}\n#else\nvec3 cloudUp(vec3 p) { return vec3(0.0, 1.0, 0.0); }\n\nfloat heightFrac(vec3 p) {\n return (p.y - uCloudBottom) / (uCloudTop - uCloudBottom);\n}\n\n// Cubic B-spline reconstruction from four bilinear taps. Bilinear leaves a\n// crease at every texel edge; the coverage threshold amplifies it, and since\n// the cover is the same at every height each crease shows as a vertical\n// streak on the cloud sides.\nvec4 weatherBSpline(sampler2D tex, vec2 uv) {\n vec2 st = uv * uWeatherSize - 0.5;\n vec2 i = floor(st);\n vec2 f = st - i;\n vec2 f2 = f * f;\n vec2 f3 = f2 * f;\n vec2 w0 = (-f3 + 3.0 * f2 - 3.0 * f + 1.0) / 6.0;\n vec2 w1 = (3.0 * f3 - 6.0 * f2 + 4.0) / 6.0;\n vec2 w3 = f3 / 6.0;\n vec2 g0 = w0 + w1;\n vec2 g1 = 1.0 - g0;\n vec2 h0 = (i - 0.5 + w1 / g0) / uWeatherSize;\n vec2 h1 = (i + 1.5 + w3 / g1) / uWeatherSize;\n return g0.y * (g0.x * textureLod(tex, h0, 0.0) + g1.x * textureLod(tex, vec2(h1.x, h0.y), 0.0))\n + g1.y * (g0.x * textureLod(tex, vec2(h0.x, h1.y), 0.0) + g1.x * textureLod(tex, h1, 0.0));\n}\n\nvec4 weatherAt2D(vec2 F) {\n vec2 uv = (F - uWeatherCenter) * uWeatherInvSpan + 0.5;\n return mix(weatherBSpline(uWeatherMap, uv), weatherBSpline(uWeatherMapNext, uv), uWeatherBlend);\n}\n\n// the clouds fade out toward the edge of the disc over the board\nfloat edgeFade(vec3 p) {\n float d = length(p.xz - uCloudCenter) / max(uCloudRadius, 1.0);\n return 1.0 - smoothstep(0.72, 1.0, d);\n}\n\n// bilinear weather for the light march (a coarse lookup is enough there)\nvec2 weatherWarp(vec3 p) {\n // A weather map is 2D. Warp it with a genuinely 3D field so the edges of\n // thick clouds round off at each height instead of forming vertical walls.\n vec2 n = textureLod(uCloudNoise, p * uCloudShapeFreq * 0.7\n + uCloudVariationOffset + uCloudWind * 0.35, 0.0).rg;\n return (n - vec2(0.65, 0.55)) * (0.35 / max(uCloudShapeFreq, 1e-6));\n}\n\nvec4 cloudWeatherLight(vec3 p, float hf) {\n vec2 F = p.xz - uFieldShift - uShearDir * (uCloudShear * hf);\n F += weatherWarp(p);\n vec2 uv = (F - uWeatherCenter) * uWeatherInvSpan + 0.5;\n vec4 w = mix(textureLod(uWeatherMap, uv, 0.0), textureLod(uWeatherMapNext, uv, 0.0), uWeatherBlend);\n return vec4(cloudCover(w) * edgeFade(p), w.g, 0.0, 0.5);\n}\n\nvec4 cloudWeather(vec3 p, float hf) {\n vec2 F = p.xz - uFieldShift;\n F += weatherWarp(p);\n vec4 w = weatherAt2D(F - uShearDir * (uCloudShear * hf));\n return vec4(cloudCover(w) * edgeFade(p), w.g, 0.0, -1.0);\n}\n\nvec3 billowPos(vec3 p, float hf) {\n vec2 F = p.xz - uFieldShift - uShearDir * (uCloudShear * 0.3 * hf);\n return vec3(F.x, p.y, F.y);\n}\n\nfloat columnNoise(vec3 p, float hf) {\n vec2 F = p.xz - uFieldShift - uShearDir * (uCloudShear * hf);\n return textureLod(uCloudNoise, vec3(F.x, uCloudBottom, F.y) * uCloudCellFreq + uCloudWind * 0.2 + uCloudVariationOffset, 0.0).r;\n}\n\nvec3 sunIrradiance(vec3 p) { return uSunLight; }\n\nvec3 skyIrradiance(vec3 p, float hf) {\n return mix(uSkyBottom, uSkyTop, sat(hf)) + vec3(0.004, 0.005, 0.008) * 6.0;\n}\n#endif\n\n#define TOP_JITTER 0.09\n\n// Density at height fraction hf of column g / cover cov; pr = billow position.\nfloat cloudShape(vec3 pr, float hf, vec4 g, float cov, float detail) {\n if (hf <= 0.0 || hf >= 1.0 || hf >= g.w + 1.1 * TOP_JITTER) return 0.0;\n vec3 noisePoint = pr * uCloudShapeFreq + uCloudWind * 0.35 + uCloudVariationOffset;\n if (uCloudNoiseType > 1.5) noisePoint *= vec3(0.55, 1.7, 1.4);\n else if (uCloudNoiseType > 0.5) noisePoint *= 0.75;\n vec2 n = textureLod(uCloudNoise, noisePoint, 0.0).rg;\n // The billows move the top ramp: per-column top heights alone have no\n // vertical structure, and seen from the side they read as vertical\n // streaks running down the whole body. The flat bases stay flat.\n float hTop = hf + (n.x - 0.55) * (2.0 * TOP_JITTER);\n float prof = smoothstep(g.x, max(g.y, 0.12), hf) * (1.0 - smoothstep(max(g.z, g.w * 0.4), g.w, hTop));\n if (prof <= 0.0) return 0.0;\n float base = sat(remap(n.y, mix(0.24, 0.38, n.x), 0.82, 0.0, 1.0));\n if (uCloudNoiseType > 1.5) base = pow(base, 1.35);\n else if (uCloudNoiseType > 0.5) base = mix(base, 0.35 + 0.65 * smoothstep(0.25, 0.75, n.x), 0.55);\n // profile BEFORE the coverage remap: each column gets its own top height\n // (cauliflower tops); coverage thresholds the billows so system fringes\n // break into cumulus fields and turn translucent\n float dens = sat(remap(base * prof, 1.0 - cov, 1.0, 0.0, 1.0)) * cov;\n if (uCloudNoiseType > 1.5) dens *= 0.62;\n if (detail <= 0.0 || dens <= 0.0) return dens;\n // erosion: wispy bottoms, billowy tops\n float hfb = textureLod(uCloudErosion, pr * uCloudDetailFreq + uCloudWind + uCloudVariationOffset, 0.0).r;\n float m = mix(hfb, 1.0 - hfb, sat(hf * 4.0));\n return sat(remap(dens, m * 0.8 * uCloudDetail * detail, 1.0, 0.0, 1.0));\n}\n\nfloat cloudDensityAt(vec3 p, float detail) {\n float hf = heightFrac(p);\n if (hf <= 0.0 || hf >= 1.0) return 0.0;\n vec4 w = cloudWeather(p, hf);\n if (w.x <= 0.01) return 0.0;\n w.w = columnNoise(p, hf);\n return cloudShape(billowPos(p, hf), hf, cloudColumnGradient(w), w.x, detail);\n}\n`;\n\nexport const REALISTIC_CLOUD_FRAGMENT = /* glsl */ `\n${REALISTIC_CLOUD_FIELD_GLSL}\n\nvec3 reconstructWorldPosition(vec2 uv, float depth) {\n vec4 clip = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);\n vec4 view = uProjectionMatrixInverse * clip;\n view.xyz /= view.w;\n return (uViewMatrixInverse * vec4(view.xyz, 1.0)).xyz;\n}\n\nvoid main() {\n vec3 ro = cameraPosition;\n vec3 rd = normalize(vWorldPos - cameraPosition);\n float thick = uCloudTop - uCloudBottom;\n\n // ---- the layer segment [t0, t1] along the ray\n float t0 = 0.0, t1 = -1.0;\n#ifdef CLOUD_PLANET\n float camR = length(ro);\n vec2 tOut = raySphere(ro, rd, uCloudTop);\n vec2 tIn = raySphere(ro, rd, uCloudBottom);\n bool hitsIn = tIn.x < tIn.y && tIn.x > 0.0;\n if (tOut.x < tOut.y && tOut.y > 0.0) {\n if (camR > uCloudTop) { t0 = tOut.x; t1 = hitsIn ? tIn.x : tOut.y; }\n else if (camR > uCloudBottom) { t0 = 0.0; t1 = hitsIn ? tIn.x : tOut.y; }\n else { t0 = tIn.y; t1 = tOut.y; }\n }\n // the sea sphere occludes the layer even where nothing wrote depth\n vec2 tg = raySphere(ro, rd, uGroundRadius);\n if (tg.x < tg.y && tg.x > 0.0) t1 = min(t1, tg.x);\n#else\n if (abs(rd.y) < 1e-5) {\n if (ro.y > uCloudBottom && ro.y < uCloudTop) { t0 = 0.0; t1 = uCloudFar; }\n } else {\n float ta = (uCloudBottom - ro.y) / rd.y;\n float tb = (uCloudTop - ro.y) / rd.y;\n t0 = max(min(ta, tb), 0.0);\n t1 = min(max(ta, tb), uCloudFar);\n }\n // clamp to the cloud disc (zero cover past it)\n {\n vec2 oc = ro.xz - uCloudCenter;\n float A = dot(rd.xz, rd.xz);\n if (A > 1e-8) {\n float B = dot(oc, rd.xz);\n float C = dot(oc, oc) - uCloudRadius * uCloudRadius;\n float disc = B * B - A * C;\n if (disc < 0.0) discard;\n float sq = sqrt(disc);\n t0 = max(t0, (-B - sq) / A);\n t1 = min(t1, (-B + sq) / A);\n } else if (dot(oc, oc) > uCloudRadius * uCloudRadius) {\n discard;\n }\n }\n#endif\n if (t1 <= t0) discard;\n\n // The unclipped segment sets the sample lattice; the opaque scene only\n // shortens it (redistributing samples per pixel would make the field jump\n // at terrain silhouettes).\n float segLen = t1 - t0;\n float tEnd = t1;\n vec2 depthUv = gl_FragCoord.xy / max(uDepthResolution, vec2(1.0));\n if (uUseDepth > 0.5 && depthUv.x >= 0.0 && depthUv.x <= 1.0 && depthUv.y >= 0.0 && depthUv.y <= 1.0) {\n float sceneDepth = texture2D(tSceneDepth, depthUv).x;\n if (sceneDepth < 0.99999) {\n vec3 sceneHit = reconstructWorldPosition(depthUv, sceneDepth);\n float hitT = dot(sceneHit - ro, rd) - uDepthBias;\n tEnd = min(tEnd, hitT);\n }\n }\n if (tEnd <= t0) discard;\n\n // Use the requested quality budget. The old billow-based estimate fell to\n // 12–24 steps in thick clouds even at 96-step quality, leaving horizontal\n // contour bands at their bases. Integrate complete, disjoint intervals.\n float steps = clamp(uCloudSteps, 12.0, float(${REALISTIC_MAX_STEPS}));\n\n float T = 1.0;\n vec3 L = vec3(0.0);\n\n // extinction per world unit: a full-thickness dense column is ~OD 14\n float sigma = 14.0 / thick * uCloudDensity;\n float cosT = dot(rd, uSunDir);\n vec3 tint = srgbToLinear(uCloudColor);\n vec3 ambTint = srgbToLinear(uCloudSkyTint);\n // multiple-scattering octaves: forward lobe = silver linings, back lobe\n // keeps the sides lit; the phases only depend on the view / sun angle\n vec3 msPh = vec3(mix(phaseHG(cosT, 0.7), phaseHG(cosT, -0.25), 0.4) + phaseHG(cosT, 0.88) * 0.3 * uCloudSilver,\n mix(phaseHG(cosT, 0.35), phaseHG(cosT, -0.125), 0.4) * 0.5,\n mix(phaseHG(cosT, 0.175), phaseHG(cosT, -0.0625), 0.4) * 0.25);\n // powder darkening eases off looking toward the sun, where thin edges glow\n float powderView = 1.0 - 0.7 * sat(cosT);\n\n for (int i = 0; i < ${REALISTIC_MAX_STEPS} + uLoopGuard; i++) {\n float intervalStart = t0 + segLen * float(i) / steps;\n if (float(i) >= steps || T < 0.01 || intervalStart >= tEnd) break;\n float ds = segLen / steps;\n float dsi = min(ds, tEnd - intervalStart);\n float t = intervalStart + dsi * 0.5;\n vec3 p = ro + rd * t;\n float hf = heightFrac(p);\n if (hf > 0.0 && hf < 1.0) {\n // one weather lookup per sample, reused by the light march\n vec4 w = cloudWeather(p, hf);\n float dens = 0.0;\n vec4 g = vec4(0.0);\n if (w.x > 0.01) {\n // Use the same column at every height; conditionally changing its\n // height partway through the march created a visible horizontal shelf.\n w.w = columnNoise(p, hf);\n g = cloudColumnGradient(w);\n // erosion fades out where the step is longer than its cells: under-\n // sampled detail only adds aliasing (streaks on long grazing rays)\n float detailW = sat(2.0 - ds * uCloudDetailFreq * 4.0);\n // Two symmetric density taps, each with its own weather and profile.\n // Lighting stays at the interval centre, so surface brightness cannot\n // turn a pixel's random sampling phase into visible grain or stripes.\n vec3 offset = rd * (dsi * 0.288675);\n dens = 0.5 * (cloudDensityAt(p - offset, detailW) + cloudDensityAt(p + offset, detailW));\n }\n if (dens > 0.003) {\n // light march toward the sun (cone-ish growing steps)\n float odL = 0.0;\n float ls = thick * 0.06;\n vec3 lp = p;\n for (int j = 0; j < 5 + uLoopGuard; j++) {\n lp += uSunDir * ls;\n float hj = heightFrac(lp);\n // past the first step the sun ray has left this column: its own\n // cover and profile (reusing the sample's column, as on a thin\n // planet shell, lit thick slabs in vertical bands per column)\n if (hj > 0.0 && hj < 1.0) {\n vec4 wl = cloudWeatherLight(lp, hj);\n wl.w = columnNoise(lp, hj);\n if (wl.x > 0.01) odL += cloudShape(billowPos(lp, hj), hj, cloudColumnGradient(wl), wl.x, j < 2 ? 1.0 : 0.0) * ls;\n }\n ls *= 1.6;\n }\n odL *= sigma;\n\n float ms = dot(msPh, exp(-odL * vec3(1.0, 0.55, 0.3025)));\n float powder = 1.0 - exp(-dens * sigma * thick * 0.15);\n vec3 up = cloudUp(p);\n vec3 sunC = sunIrradiance(p);\n // less of the sky dome reaches deep under a tall top\n float skyVis = mix(0.4, 1.0, sat(hf / max(g.w, 0.05)));\n vec3 amb = skyIrradiance(p, hf) / PI * skyVis * ambTint;\n // light bounced off the sunlit ground below the bases\n vec3 bounce = sunC * (max(dot(up, uSunDir), 0.0) * 0.05 * (1.0 - hf));\n vec3 S = (sunC * ms * mix(1.0, mix(0.45, 1.0, powder), powderView) * 2.8 + amb * 1.1 + bounce) * tint;\n\n float Tr = exp(-dens * sigma * dsi);\n L += T * S * (1.0 - Tr);\n T *= Tr;\n }\n }\n }\n\n float alpha = 1.0 - T;\n if (alpha < 0.003) discard;\n // the layer is drawn over an already display-encoded frame: expose + ACES +\n // sRGB here (Procedural Planets does it once in its composite)\n vec3 col = aces(L / alpha * uExposure * 0.85);\n gl_FragColor = vec4(linearToSrgb(col), alpha);\n}\n`;\n","import * as THREE from 'three';\nimport { CLOUD_NOISE_GLSL, CLOUD_FIELD_GLSL, CLOUD_SLAB_GLSL, CLOUD_VOLUME_GLSL } from './cloudGLSL.js';\nimport { REALISTIC_CLOUD_FIELD_GLSL } from './realistic/realisticCloudGLSL.js';\n\nexport const TERRAIN_CLOUD_SHADOW_GLSL = /* glsl */ `\n// Tile occupancy occupies R; cloud optical depth uses its otherwise unused G.\n// Sharing the map keeps the terrain within the WebGL texture-unit budget.\n#define uTerrainCloudShadowTex uTileOccupancy\nuniform float uTerrainCloudShadowEnabled;\nuniform float uTerrainCloudShadowStrength;\nuniform vec2 uTerrainCloudShadowCenter;\nuniform float uTerrainCloudShadowExtent;\nuniform float uTerrainCloudShadowAltitude;\nuniform float uTerrainCloudShadowPlanet;\n\nvec2 cloudShadowOctUV(vec3 d) {\n d /= abs(d.x) + abs(d.y) + abs(d.z);\n vec2 p = d.xy;\n if (d.z < 0.0) p = (1.0 - abs(p.yx)) * mix(vec2(-1.0), vec2(1.0), step(vec2(0.0), p));\n return p * 0.5 + 0.5;\n}\n\nfloat terrainCloudShadow(vec3 worldPos) {\n if (uTerrainCloudShadowEnabled < 0.5) return 0.0;\n vec2 uv;\n if (uTerrainCloudShadowPlanet > 0.5) {\n vec3 up = normalize(worldPos);\n if (dot(up, uSunDir) <= 0.01) return 0.0;\n float b = dot(worldPos, uSunDir);\n float r = uTerrainCloudShadowAltitude;\n float t = max(0.0, -b + sqrt(max(b * b - dot(worldPos, worldPos) + r * r, 0.0)));\n uv = cloudShadowOctUV(normalize(worldPos + uSunDir * t));\n } else {\n if (uSunDir.y <= 0.01 || worldPos.y >= uTerrainCloudShadowAltitude) return 0.0;\n float rise = uTerrainCloudShadowAltitude - worldPos.y;\n vec2 xz = worldPos.xz + uSunDir.xz * rise / max(uSunDir.y, 0.05);\n uv = (xz - uTerrainCloudShadowCenter) / (2.0 * uTerrainCloudShadowExtent) + 0.5;\n if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) return 0.0;\n }\n vec2 texel = 1.5 / vec2(textureSize(uTerrainCloudShadowTex, 0));\n float shadow = 0.25 * (\n texture2D(uTerrainCloudShadowTex, uv + texel).g\n + texture2D(uTerrainCloudShadowTex, uv - texel).g\n + texture2D(uTerrainCloudShadowTex, uv + texel * vec2(1.0, -1.0)).g\n + texture2D(uTerrainCloudShadowTex, uv + texel * vec2(-1.0, 1.0)).g);\n return shadow * uTerrainCloudShadowStrength;\n}\n`;\n\nconst vertexShader = /* glsl */ `\nvarying vec2 vUv;\nvoid main() { vUv = uv; gl_Position = vec4(position.xy, 0.0, 1.0); }\n`;\n\nfunction fragment(realistic, planet) {\n return /* glsl */ `\nprecision highp float;\n${realistic ? REALISTIC_CLOUD_FIELD_GLSL : `\n#define CLOUD_OCTAVES 3\n#define CLOUD_DETAIL_OCTAVES 0\n#define CLOUD_USE_EROSION 0\n#define CLOUD_LIGHT_STEPS 1\n${CLOUD_NOISE_GLSL}\n${CLOUD_FIELD_GLSL}\n${planet ? CLOUD_VOLUME_GLSL : CLOUD_SLAB_GLSL}`}\nvarying vec2 vUv;\nuniform vec2 uShadowCenter;\nuniform float uShadowExtent;\nuniform vec3 uShadowSun;\nuniform float uShadowBottom;\nuniform float uShadowTop;\nuniform sampler2D uShadowTileOccupancy;\n\nvec3 shadowOctDir(vec2 p) {\n vec3 d = vec3(p, 1.0 - abs(p.x) - abs(p.y));\n if (d.z < 0.0) d.xy = (1.0 - abs(d.yx)) * mix(vec2(-1.0), vec2(1.0), step(vec2(0.0), d.xy));\n return normalize(d);\n}\n\nvoid main() {\n float tile = texture2D(uShadowTileOccupancy, vUv).r;\n float span = uShadowTop - uShadowBottom;\n vec3 ro;\n float distance;\n ${planet ? `\n vec3 up = shadowOctDir(vUv * 2.0 - 1.0);\n ro = up * uShadowBottom;\n float mu = dot(up, uShadowSun);\n if (mu <= 0.01) { gl_FragColor = vec4(tile, 0.0, 0.0, 1.0); return; }\n float b = dot(ro, uShadowSun);\n distance = -b + sqrt(max(b*b - dot(ro, ro) + uShadowTop*uShadowTop, 0.0));\n ` : `\n vec2 xz = uShadowCenter + (vUv * 2.0 - 1.0) * uShadowExtent;\n ro = vec3(xz.x, uShadowBottom, xz.y);\n distance = span / max(uShadowSun.y, 0.05);\n `}\n float od = 0.0;\n for (int i = 0; i < 12 + uLoopGuard; i++) {\n vec3 p = ro + uShadowSun * (distance * (float(i) + 0.5) / 12.0);\n od += ${realistic ? 'cloudDensityAt(p, 0.0)' : 'cloudDensity(p)'};\n }\n float sigma = ${realistic ? '14.0 * uCloudDensity / max(span, 1.0)' : 'uCloudExtinction'};\n float shadow = 1.0 - exp(-od * distance / 12.0 * sigma);\n gl_FragColor = vec4(tile, shadow, 0.0, 1.0);\n}\n`;\n}\n\n/** Small GPU map of the visible density integrated toward the sun. */\nexport class CloudShadowPass {\n constructor({ realistic = false, planet = false } = {}) {\n this.planet = planet;\n const size = planet ? 384 : 192;\n this.target = new THREE.WebGLRenderTarget(size, size, {\n minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter,\n depthBuffer: false, generateMipmaps: false,\n });\n this.material = new THREE.ShaderMaterial({\n vertexShader, fragmentShader: fragment(realistic, planet),\n defines: planet && realistic ? { CLOUD_PLANET: 1 } : {},\n depthTest: false, depthWrite: false, blending: THREE.NoBlending,\n });\n this.material.name = `cloud-shadow.${realistic ? 'realistic' : 'legacy'}.${planet ? 'planet' : 'flat'}`;\n this.mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), this.material);\n this.mesh.frustumCulled = false;\n this.scene = new THREE.Scene();\n this.scene.add(this.mesh);\n this.camera = new THREE.Camera();\n this.state = { texture: this.target.texture, center: new THREE.Vector2(), extent: 1, altitude: 0, planet };\n this.uniforms = {\n uShadowCenter: { value: this.state.center }, uShadowExtent: { value: 1 },\n uShadowSun: { value: new THREE.Vector3() },\n uShadowBottom: { value: 0 }, uShadowTop: { value: 1 },\n uShadowTileOccupancy: { value: null },\n };\n this._emptyTiles = new THREE.DataTexture(new Uint8Array([255, 0, 0, 255]), 1, 1);\n this._emptyTiles.needsUpdate = true;\n }\n\n render(renderer, cloudUniforms, sun, tiles) {\n if (!renderer || !sun || (!this.planet && sun.y <= 0.01)) return null;\n const u = this.uniforms;\n const bottom = (cloudUniforms.uCloudBottom || cloudUniforms.uCloudInner).value;\n const top = (cloudUniforms.uCloudTop || cloudUniforms.uCloudOuter).value;\n const center = cloudUniforms.uCloudCenter?.value;\n if (center) this.state.center.set(center.x, center.z ?? center.y);\n const radius = cloudUniforms.uCloudRadius?.value ?? 1;\n this.state.extent = radius + (top - bottom) * Math.min(8, Math.hypot(sun.x, sun.z) / Math.max(sun.y, 0.05));\n this.state.altitude = bottom;\n u.uShadowExtent.value = this.state.extent;\n u.uShadowBottom.value = bottom;\n u.uShadowTop.value = top;\n u.uShadowSun.value.copy(sun).normalize();\n u.uShadowTileOccupancy.value = tiles || this._emptyTiles;\n this.material.uniforms = { ...cloudUniforms, ...u };\n const target = renderer.getRenderTarget();\n const face = renderer.getActiveCubeFace?.() ?? 0;\n const mip = renderer.getActiveMipmapLevel?.() ?? 0;\n const xr = renderer.xr.enabled;\n const autoClear = renderer.autoClear;\n try {\n renderer.xr.enabled = false;\n renderer.autoClear = true;\n renderer.setRenderTarget(this.target);\n renderer.render(this.scene, this.camera);\n } finally {\n renderer.setRenderTarget(target, face, mip);\n renderer.xr.enabled = xr;\n renderer.autoClear = autoClear;\n }\n return this.state;\n }\n\n dispose() {\n this.target.dispose(); this.material.dispose(); this.mesh.geometry.dispose(); this._emptyTiles.dispose();\n }\n}\n\nexport function renderTerrainCloudShadow(layer, sun, planet = false, tiles = null) {\n if (!layer._renderer || !layer.active) return null;\n if (layer.renderMode === 'realistic' && !layer.material.uniforms.uCloudNoise.value) return null;\n layer._shadowPass ??= new CloudShadowPass({ realistic: layer.renderMode === 'realistic', planet });\n return layer._shadowPass.render(layer._renderer, layer.material.uniforms, sun, tiles);\n}\n","// ============================================================================\n// Mini Terrain section walls — stylised strata for the opaque cut.\n//\n// The cut wall IS the terrain's own outer perimeter skirt (see TerrainMaterial\n// vertex: outer skirt vertices drop to uPlinthBaseY), so its top edge follows\n// the rendered surface exactly at every resolution. This block only colours\n// it: a dark topsoil band under the local surface (sandy sediment where the\n// surface is submerged), soft undulating strata by height, a deep base, and a\n// face-normal key light. No per-stratum geometry, no textures, no loops.\n//\n// uMiniCut = 0 keeps the legacy flat plinth colour for every other mode.\n// ============================================================================\n\nexport const MINI_CUT_GLSL = /* glsl */ `\nuniform float uMiniCut;\nuniform float uMiniHexagon;\nuniform vec3 uMiniCutTopsoil;\nuniform vec3 uMiniCutBandA;\nuniform vec3 uMiniCutBandB;\nuniform vec3 uMiniCutDeep;\nuniform vec3 uMiniCutSediment;\nuniform vec4 uMiniCutParams; // bands, base Y, topsoil depth, relief height\n\nfloat miniCutHash(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33);\n return fract((p3.x + p3.y) * p3.z);\n}\n\nfloat miniCutNoise(vec2 p) {\n vec2 i = floor(p);\n vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n float a = miniCutHash(i);\n float b = miniCutHash(i + vec2(1.0, 0.0));\n float c = miniCutHash(i + vec2(0.0, 1.0));\n float d = miniCutHash(i + vec2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\n// Linear colour of the cut at world position wp under a surface of height rimH.\nvec3 miniCutAlbedo(vec3 wp, float rimH, float seaLevel) {\n bool xFace = abs(wp.x) > abs(wp.z);\n float along = xFace ? wp.z : wp.x;\n // round footprint (bevel = half-width): seamless noise around the wall\n bool roundWall = uMiniBevelRounded > 0.5 && uMiniTerrainBevel > uBoardHalf - 0.5;\n float baseY = uMiniCutParams.y;\n float reliefH = max(uMiniCutParams.w, 1.0);\n float t = clamp((wp.y - baseY) / (reliefH * 1.15), 0.0, 1.0);\n\n // gently undulating strata: low-frequency warp along the face\n float warp = (miniCutNoise(vec2(along * 0.010, 3.7)) - 0.5) * 0.9\n + sin(along * 0.0055 + 1.3) * 0.30;\n if (roundWall) {\n float lobes = max(1.0, floor(uBoardHalf * 0.0055 + 0.5));\n warp = (miniCutNoise(wp.xz * 0.010 + 3.7) - 0.5) * 0.9\n + sin(atan(wp.z, wp.x) * lobes + 1.3) * 0.30;\n along = atan(wp.z, wp.x) * uBoardHalf;\n }\n float band = fract(t * uMiniCutParams.x + warp * 0.22);\n float stripe = smoothstep(0.30, 0.46, band) - smoothstep(0.80, 0.97, band);\n vec3 col = mix(uMiniCutBandA, uMiniCutBandB, stripe);\n // thin darker seams between layers\n col *= 1.0 - 0.10 * (smoothstep(0.92, 1.0, band) + smoothstep(0.08, 0.0, band));\n col = mix(uMiniCutDeep, col, smoothstep(0.02, 0.32, t));\n\n // topsoil under land, sandy sediment under water\n float below = rimH - wp.y;\n float submerged = smoothstep(seaLevel + 1.5, seaLevel - 3.0, rimH);\n vec3 top = mix(uMiniCutTopsoil, uMiniCutSediment, submerged);\n float topNoise = roundWall ? miniCutNoise(wp.xz * 0.03 + 9.1) : miniCutNoise(vec2(along * 0.03, 9.1));\n float topDepth = uMiniCutParams.z * (0.75 + 0.5 * topNoise);\n col = mix(col, top, 1.0 - smoothstep(topDepth * 0.55, topDepth, below));\n\n // fine grain so large faces never read as flat plastic\n col *= 0.93 + 0.12 * miniCutNoise(vec2(along * 0.22, wp.y * 0.30));\n return col;\n}\n\n// Final display colour (manual gamma, matching the terrain output).\nvec3 miniCutShade(vec3 wp, float rimH, float seaLevel, vec3 sunDir) {\n vec3 albedo = miniCutAlbedo(wp, rimH, seaLevel);\n vec3 n = abs(wp.x) > abs(wp.z) ? vec3(sign(wp.x), 0.0, 0.0) : vec3(0.0, 0.0, sign(wp.z));\n if (uMiniTerrainBevel > 0.0 && min(abs(wp.x), abs(wp.z)) > uBoardHalf - uMiniTerrainBevel + 0.01) {\n n = normalize(vec3(sign(wp.x), 0.0, sign(wp.z)));\n if (uMiniBevelRounded > 0.5) {\n vec2 radial = sign(wp.xz) * max(abs(wp.xz) - (uBoardHalf - uMiniTerrainBevel), 0.0);\n n = normalize(vec3(radial.x, 0.0, radial.y));\n }\n }\n if (uMiniHexagon > 0.5) {\n vec2 q = abs(wp.xz);\n n = q.x > dot(q, vec2(0.5, 0.866025404))\n ? vec3(sign(wp.x), 0.0, 0.0)\n : vec3(sign(wp.x) * 0.5, 0.0, sign(wp.z) * 0.866025404);\n }\n float ndl = max(dot(n, normalize(sunDir)), 0.0);\n // soft ambient with a slight sky/ground split + key light\n float ambient = 0.46 + 0.08 * clamp((wp.y - uMiniCutParams.y) / max(uMiniCutParams.w, 1.0), 0.0, 1.0);\n vec3 lit = albedo * (uTerrainSkyAmb * ambient + uTerrainBounce * 0.25\n + uTerrainSunCol * uTerrainSunIntensity * 0.72 * ndl);\n return pow(max(lit, vec3(0.0)), vec3(1.0 / 2.2));\n}\n`;\n\nconst srgbToLinear = (c) => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);\n\n/** Linear RGB triplet for a stored sRGB [0..1] colour. */\nexport function miniCutColor(rgb) {\n return rgb.map((c) => srgbToLinear(Math.max(0, Math.min(1, c))));\n}\n","// ============================================================================\n// Close-range terrain detail layer.\n// Procedural world-space/triplanar detail enriches the existing biome albedo\n// near the camera without replacing the global procedural terrain identity.\n// ============================================================================\n\nexport const TERRAIN_DETAIL_GLSL = /* glsl */ `\nuniform float uTerrainDetailQuality; // 0 off, 1 low, 2 medium, 3 high\nuniform float uTerrainDetailScale; // world-space noise frequency\nuniform float uTerrainDetailStrength; // albedo detail strength\nuniform float uTerrainDetailNormalStrength; // procedural normal strength\nuniform float uTerrainDetailNear;\nuniform float uTerrainDetailFar;\nuniform float uTerrainRockSlope;\nuniform float uTerrainRockSharpness;\nuniform float uTerrainTriplanar;\nuniform float uTerrainShoreRange;\nuniform float uTerrainShoreWetness;\nuniform float uTerrainDetailOpacity; // 0..1 master mix over the whole detail layer\nuniform float uTerrainMicroDetail; // 0..1 high-frequency close-up grain amount\nuniform float uTerrainMacroVariation; // 0..1 large-scale weathering / patch breakup\nuniform float uTerrainDetailDebug; // 0 off, 1 slope, 2 rock, 3 shore, 4 fade, 5 detail, 6 albedo, 7 normal\nuniform float uVisualTerrainColorVariation;\nuniform float uVisualTerrainHeightDetail;\nuniform float uVisualWetShoreStrength;\nuniform float uVisualRockDetail;\nuniform float uVisualSoilDetail;\nuniform float uVisualSandDetail;\nuniform float uVisualWetSandRange;\nuniform highp sampler2DArray uDetailPageArray;\nuniform vec3 uDetailPageCoords[16];\nuniform float uDetailPageBasis;\nuniform float uDetailGlobalBlend;\n\nvec2 detailPagePosition(vec3 p) {\n if (uDetailPageBasis > 1.5) return p.xy;\n if (uDetailPageBasis > 0.5) return p.yz;\n return p.xz;\n}\n\nfloat detailPageSlot(vec2 tile) {\n return mod(tile.x, 4.0) + mod(tile.y, 4.0) * 4.0;\n}\n\nfloat detailPageAvailable(vec3 p) {\n vec2 tile = floor(detailPagePosition(p) / 128.0);\n int slot = int(detailPageSlot(tile));\n vec3 page = uDetailPageCoords[slot];\n return (1.0 - step(0.1, length(page.xy - tile))) * page.z;\n}\n\nvec4 detailPageSample(vec3 p) {\n vec2 coord = detailPagePosition(p);\n vec2 tile = floor(coord / 128.0);\n int slot = int(detailPageSlot(tile));\n vec3 page = uDetailPageCoords[slot];\n if (length(page.xy - tile) > 0.1) return vec4(0.5);\n vec2 uv = fract(coord / 128.0);\n // Explicit LOD: the page array has no mips, and an implicit-derivative\n // fetch here would force the compiler to flatten the detail branches.\n return textureLod(uDetailPageArray, vec3(uv, float(slot)), 0.0);\n}\n\nfloat terrainDetailEnabled() {\n return step(0.5, uTerrainDetailQuality) * uDetailGlobalBlend;\n}\n\nfloat terrainDetailFadeAt(vec3 worldPos) {\n float d = length(cameraPosition - worldPos);\n float fade = 1.0 - smoothstep(uTerrainDetailNear, max(uTerrainDetailNear + 1.0, uTerrainDetailFar), d);\n // master opacity scales the whole layer — albedo strength and the normal pass\n // both gate on this fade, so one dial fades the entire detail contribution.\n return fade * terrainDetailEnabled() * detailPageAvailable(worldPos)\n * clamp(uTerrainDetailOpacity, 0.0, 1.0);\n}\n\nfloat terrainDetailQualityFactor() {\n return clamp(uTerrainDetailQuality / 3.0, 0.0, 1.0);\n}\n\nfloat terrainDetailNoise(vec3 worldPos, vec3 n, float scale) {\n return detailPageSample(worldPos).r;\n}\n\n// Relief height for the normal pass — the broad fine band plus an optional\n// crisp micro band so close-up bumps gain high-frequency structure on top of\n// the larger shape. Shared by both the flat-board and planet normal helpers so\n// they read identical surface detail. Sampled 3x by the caller (center + 2\n// neighbours) to derive the gradient.\nfloat terrainDetailRelief(vec3 worldPos, vec3 n, float scale) {\n vec4 page = detailPageSample(worldPos);\n float fine = page.r;\n float micro = clamp(uTerrainMicroDetail, 0.0, 1.0);\n float heightDetail = clamp(uVisualTerrainHeightDetail, 0.0, 1.0);\n if (micro <= 0.001 && heightDetail <= 0.001) return fine;\n float coarse = page.g;\n float hi = page.b;\n return fine\n + (hi - 0.5) * micro * 0.55\n + (coarse - 0.5) * heightDetail * 0.42;\n}\n\n// Multi-octave detail sample: four decorrelated frequency bands reused across\n// every biome path so albedo variation has both crisp grain and large organic\n// patches without recomputing noise per material.\nstruct DetailSample {\n float fine; // primary close grain\n float coarse; // broad clumping\n float micro; // high-frequency speckle / crisp detail\n float macro; // very low frequency weathering patches\n};\n\nDetailSample terrainDetailSampleAt(vec3 worldPos, vec3 n, float scale) {\n vec4 page = detailPageSample(worldPos);\n DetailSample d;\n d.fine = page.r;\n d.coarse = page.g;\n d.micro = page.b;\n d.macro = page.a;\n return d;\n}\n\nfloat terrainRockMask(float slope, float jitter) {\n float width = max(0.04, uTerrainRockSharpness);\n return smoothstep(uTerrainRockSlope - width, uTerrainRockSlope + width, slope + jitter * 0.06);\n}\n\nfloat terrainShoreMask(float hRel) {\n float shoreRange = max(uTerrainShoreRange + uVisualWetSandRange * 0.35, 0.01);\n return 1.0 - smoothstep(0.0, shoreRange, abs(hRel));\n}\n\nstruct TerrainDetailResult {\n vec3 albedo;\n float detail;\n float fade;\n float rockMask;\n float shoreMask;\n};\n\nTerrainDetailResult applyTerrainDetailLayer(\n TerrainColorResult tc,\n Climate cl,\n BiomeWeights bw,\n vec3 worldPos,\n vec3 normalGeo,\n float hC,\n float hRel,\n float h01,\n float slope,\n float jitter\n) {\n TerrainDetailResult outD;\n float fade = terrainDetailFadeAt(worldPos);\n if (fade <= 0.001) {\n outD.albedo = tc.albedo;\n outD.detail = 0.5;\n outD.fade = 0.0;\n outD.rockMask = tc.rockBlend;\n outD.shoreMask = terrainShoreMask(hRel);\n return outD;\n }\n float quality = terrainDetailQualityFactor();\n float scale = uTerrainDetailScale * mix(0.55, 1.25, quality);\n\n DetailSample ds = terrainDetailSampleAt(worldPos, normalGeo, scale);\n float fine = ds.fine;\n float coarse = ds.coarse;\n float micro = clamp(uTerrainMicroDetail, 0.0, 1.0);\n float macroAmt = clamp(uTerrainMacroVariation + uVisualTerrainColorVariation * 0.45, 0.0, 1.35);\n\n // close bands form the base grain; the micro band adds crisp speckle that\n // the Micro Detail slider dials up for sharper close-up texture.\n float grain = clamp(fine * 0.60 + coarse * 0.26 + ds.micro * (0.14 + 0.10 * micro), 0.0, 1.0);\n float signedGrain = grain * 2.0 - 1.0;\n float microSigned = ds.micro * 2.0 - 1.0;\n float macroSigned = ds.macro * 2.0 - 1.0; // -1..1 large weathering patch field\n\n float rockMask = max(tc.rockBlend, terrainRockMask(slope, jitter));\n float shoreMask = terrainShoreMask(hRel);\n float desertGround = clamp(max(bw.desert, tc.sandBand > 0.0 ? 1.0 - smoothstep(tc.sandBand * 0.4, tc.sandBand, hRel) : 0.0), 0.0, 1.0);\n float wetGround = clamp(max(bw.wetland, shoreMask * 0.65), 0.0, 1.0);\n float vegGround = clamp((1.0 - desertGround) * (1.0 - bw.canyon) * (1.0 - tc.snow) * tc.flatness * smoothstep(0.20, 0.72, cl.moist), 0.0, 1.0);\n\n // desert wind ripples: anisotropic crests along a fixed wind axis, meandered\n // by the coarse/macro bands so they curve organically instead of striping.\n vec2 windDir = normalize(vec2(0.86, 0.51));\n float ripplePhase = dot(worldPos.xz, windDir) * scale * 7.5 + coarse * 6.5 + ds.macro * 3.0;\n float ripple = sin(ripplePhase) * 0.5 + 0.5;\n ripple *= ripple; // sharpen crests\n float dunes = (ripple - 0.5) * desertGround * (1.0 - rockMask);\n\n // rock stratification + canyon banding driven by height with macro warble\n float strata = 0.5 + 0.5 * sin(h01 * 120.0 + coarse * 4.0 + macroSigned * 2.0);\n float canyonBands = 0.5 + 0.5 * sin(h01 * 210.0 + coarse * 5.0);\n\n // large-scale weathering: sign carries a faint hue drift, magnitude the value\n float weather = macroSigned * macroAmt;\n\n float sandDetail = 1.0 + clamp(uVisualSandDetail, 0.0, 1.0) * 0.45;\n float soilDetail = 1.0 + clamp(uVisualSoilDetail, 0.0, 1.0) * 0.40;\n float rockDetail = 1.0 + clamp(uVisualRockDetail, 0.0, 1.0) * 0.55;\n\n vec3 sandTint = mix(uColSand * 0.78, uColDune * 1.12, clamp((grain - 0.5) * sandDetail + 0.5, 0.0, 1.0));\n sandTint *= 1.0 + dunes * 0.22; // ripple crest/trough shading\n vec3 grassTint = mix(uColDryGrass * 0.82, uColForest * 0.92, clamp((grain - 0.5) * soilDetail + 0.5, 0.0, 1.0)) * mix(0.96, 1.08, coarse);\n grassTint = mix(grassTint, mix(uColDryGrass, uColGrass, grain),\n smoothstep(0.40, 0.70, ds.macro) * 0.35); // dry vs. lush clumps\n vec3 mudTint = mix(uColSwamp * 0.62, uColSand * 0.55, clamp((grain - 0.5) * soilDetail + 0.5, 0.0, 1.0));\n vec3 rockTint = mix(uColRock * 0.68, uColRockHi * 1.10, clamp((grain - 0.5) * rockDetail + 0.5, 0.0, 1.0));\n rockTint = mix(rockTint, rockTint * mix(0.82, 1.12, strata), 0.55); // strata banding\n vec3 canyonTint = mix(uColRedRock * 0.70, uColRedRock2 * 1.12, canyonBands);\n vec3 snowTint = mix(uColSnow * 0.84, vec3(0.90, 0.97, 1.0), grain);\n\n vec3 materialTint = mix(tc.albedo, grassTint, vegGround * 0.42);\n materialTint = mix(materialTint, sandTint, desertGround * (1.0 - rockMask) * 0.52);\n materialTint = mix(materialTint, mudTint, wetGround * (1.0 - rockMask) * 0.38);\n materialTint = mix(materialTint, mix(rockTint, canyonTint, bw.canyon), rockMask * 0.66);\n materialTint = mix(materialTint, snowTint, tc.snow * 0.42);\n\n // weathering breakup so each biome shows organic patches, not a flat wash\n materialTint *= 1.0 + weather * (0.16 + 0.10 * rockMask);\n materialTint *= vec3(1.0 + weather * 0.05, 1.0, 1.0 - weather * 0.04);\n\n float crack = smoothstep(0.16, 0.0, abs(microSigned)) * rockMask;\n float fleck = (ds.micro - 0.5) * 2.0;\n vec3 detailed = materialTint;\n detailed *= 1.0 + signedGrain * (0.055 + 0.085 * rockMask + 0.030 * vegGround);\n detailed *= 1.0 + microSigned * micro * (0.05 + 0.06 * rockMask); // crisp close grain\n detailed *= 1.0 - crack * 0.22;\n detailed += fleck * 0.028 * (desertGround + vegGround * 0.5) * (1.0 - rockMask);\n float wetShore = clamp(uTerrainShoreWetness + uVisualWetShoreStrength * 0.55, 0.0, 1.4);\n detailed = mix(detailed, detailed * mix(0.68, 0.92, grain), shoreMask * wetShore);\n\n float strength = clamp(uTerrainDetailStrength, 0.0, 2.0) * fade;\n outD.albedo = mix(tc.albedo, max(detailed, vec3(0.0)), strength);\n outD.detail = grain;\n outD.fade = fade;\n outD.rockMask = rockMask;\n outD.shoreMask = shoreMask;\n return outD;\n}\n`;\n","// ============================================================================\n// Biome color palette — linear RGB slots mapped to terrain shader uniforms.\n// Same terrain shape can be recolored by swapping this palette live.\n// ============================================================================\n\nexport const PALETTE_KEYS = [\n 'deep', 'shallow', 'sand', 'dune', 'dryGrass', 'grass', 'forest', 'jungle',\n 'swamp', 'tundra', 'redRock', 'redRock2', 'rock', 'rockHi', 'snow', 'foam',\n];\n\n/** Default Earth-like palette (matches original hardcoded shader constants). */\nexport const EARTH_PALETTE = {\n deep: [0.012, 0.075, 0.140],\n shallow: [0.060, 0.290, 0.330],\n sand: [0.560, 0.470, 0.300],\n dune: [0.620, 0.490, 0.290],\n dryGrass: [0.380, 0.330, 0.150],\n grass: [0.130, 0.260, 0.085],\n forest: [0.052, 0.140, 0.055],\n jungle: [0.035, 0.125, 0.045],\n swamp: [0.090, 0.130, 0.070],\n tundra: [0.300, 0.290, 0.240],\n redRock: [0.420, 0.235, 0.140],\n redRock2: [0.560, 0.370, 0.210],\n rock: [0.260, 0.235, 0.215],\n rockHi: [0.380, 0.365, 0.355],\n snow: [0.870, 0.890, 0.930],\n foam: [0.820, 0.900, 0.940],\n};\n\nexport function clonePalette(src = EARTH_PALETTE) {\n const out = {};\n for (const k of PALETTE_KEYS) out[k] = [...src[k]];\n return out;\n}\n\nexport function palettesEqual(a, b) {\n if (!a || !b) return false;\n return PALETTE_KEYS.every((k) =>\n Math.abs(a[k][0] - b[k][0]) < 1e-4\n && Math.abs(a[k][1] - b[k][1]) < 1e-4\n && Math.abs(a[k][2] - b[k][2]) < 1e-4\n );\n}\n\n/** Convert #rrggbb or [r,g,b] 0-255 to linear 0-1 RGB triple. */\nexport function parseColor(input) {\n if (Array.isArray(input)) {\n const scale = input.some((v) => v > 1) ? 1 / 255 : 1;\n return input.map((v) => Math.max(0, Math.min(1, v * scale)));\n }\n const hex = String(input).replace('#', '');\n if (hex.length !== 6) return [1, 1, 1];\n return [\n parseInt(hex.slice(0, 2), 16) / 255,\n parseInt(hex.slice(2, 4), 16) / 255,\n parseInt(hex.slice(4, 6), 16) / 255,\n ];\n}\n\nexport function colorToHex(rgb) {\n const c = (v) => Math.round(Math.max(0, Math.min(1, v)) * 255).toString(16).padStart(2, '0');\n return `#${c(rgb[0])}${c(rgb[1])}${c(rgb[2])}`;\n}\n","import * as THREE from 'three';\nimport { PALETTE_KEYS } from './ColorPalette.js';\n\n// ============================================================================\n// Maps palette + style tuning into shared terrain/water shader uniforms.\n// Color changes are live — no geometry rebuild required.\n// ============================================================================\n\nconst UNIFORM_MAP = {\n deep: 'uColDeep',\n shallow: 'uColShallow',\n sand: 'uColSand',\n dune: 'uColDune',\n dryGrass: 'uColDryGrass',\n grass: 'uColGrass',\n forest: 'uColForest',\n jungle: 'uColJungle',\n swamp: 'uColSwamp',\n tundra: 'uColTundra',\n redRock: 'uColRedRock',\n redRock2: 'uColRedRock2',\n rock: 'uColRock',\n rockHi: 'uColRockHi',\n snow: 'uColSnow',\n foam: 'uColFoam',\n};\n\nexport function createPaletteUniforms() {\n const u = {\n uPaletteSaturation: { value: 1.0 },\n uPaletteContrast: { value: 1.0 },\n uPaletteTint: { value: new THREE.Vector3(1, 1, 1) },\n uTerrainSunCol: { value: new THREE.Vector3(1.0, 0.94, 0.82) },\n uTerrainSunIntensity: { value: 1.25 },\n uTerrainSkyAmb: { value: new THREE.Vector3(0.36, 0.46, 0.62) },\n uTerrainBounce: { value: new THREE.Vector3(0.20, 0.16, 0.11) },\n };\n for (const key of PALETTE_KEYS) {\n u[UNIFORM_MAP[key]] = { value: new THREE.Vector3(0, 0, 0) };\n }\n return u;\n}\n\nexport function applyPlanetStyleToUniforms(uniforms, planetStyle) {\n if (!uniforms || !planetStyle) return;\n\n const pal = planetStyle.palette;\n for (const key of PALETTE_KEYS) {\n const uni = UNIFORM_MAP[key];\n if (uniforms[uni] && pal[key]) {\n uniforms[uni].value.set(pal[key][0], pal[key][1], pal[key][2]);\n }\n }\n\n if (uniforms.uPaletteSaturation) uniforms.uPaletteSaturation.value = planetStyle.paletteSaturation ?? 1;\n if (uniforms.uPaletteContrast) uniforms.uPaletteContrast.value = planetStyle.paletteContrast ?? 1;\n if (uniforms.uPaletteTint && planetStyle.paletteTint) {\n uniforms.uPaletteTint.value.set(\n planetStyle.paletteTint[0],\n planetStyle.paletteTint[1],\n planetStyle.paletteTint[2]\n );\n }\n if (uniforms.uTerrainSunCol && planetStyle.sunColor) {\n uniforms.uTerrainSunCol.value.set(planetStyle.sunColor[0], planetStyle.sunColor[1], planetStyle.sunColor[2]);\n }\n if (uniforms.uTerrainSunIntensity) {\n uniforms.uTerrainSunIntensity.value = planetStyle.sunIntensity ?? 1.25;\n }\n if (uniforms.uTerrainSkyAmb && planetStyle.skyAmbient) {\n uniforms.uTerrainSkyAmb.value.set(planetStyle.skyAmbient[0], planetStyle.skyAmbient[1], planetStyle.skyAmbient[2]);\n }\n if (uniforms.uTerrainBounce && planetStyle.groundBounce) {\n uniforms.uTerrainBounce.value.set(\n planetStyle.groundBounce[0],\n planetStyle.groundBounce[1],\n planetStyle.groundBounce[2]\n );\n }\n}\n","import { clonePalette } from './ColorPalette.js';\n\n// ============================================================================\n// Planet style configuration — color layer separate from terrain shape.\n// ============================================================================\n\nexport const DEFAULT_PLANET_STYLE = {\n planetPreset: 'earth',\n palettePreset: 'earth',\n noisePreset: 'default',\n palette: clonePalette(),\n paletteSaturation: 1.0,\n paletteContrast: 1.0,\n paletteTint: [1.0, 1.0, 1.0],\n sunColor: [1.0, 0.94, 0.82],\n sunIntensity: 1.25,\n skyAmbient: [0.36, 0.46, 0.62],\n groundBounce: [0.20, 0.16, 0.11],\n fogTint: null, // null = use engine default\n skyTint: null,\n customEdits: false,\n};\n\nexport function clonePlanetStyle(src = DEFAULT_PLANET_STYLE) {\n return {\n ...src,\n palette: clonePalette(src.palette),\n paletteTint: [...src.paletteTint],\n sunColor: [...src.sunColor],\n skyAmbient: [...src.skyAmbient],\n groundBounce: [...src.groundBounce],\n fogTint: src.fogTint ? [...src.fogTint] : null,\n skyTint: src.skyTint ? [...src.skyTint] : null,\n };\n}\n\n/** Keys owned by planet style (never trigger geometry rebuild). */\nexport const STYLE_ONLY_KEYS = new Set([\n 'planetPreset', 'palettePreset', 'noisePreset', 'palette',\n 'paletteSaturation', 'paletteContrast', 'paletteTint',\n 'sunColor', 'sunIntensity', 'skyAmbient', 'groundBounce', 'fogTint', 'skyTint', 'customEdits',\n]);\n","import { TERRAIN_CLOUD_SHADOW_GLSL } from '../sky/CloudShadowPass.js';\nimport { installSurfaceMaterialBackend } from './surface/SurfaceArrayGLSL.js';\nimport * as THREE from 'three';\nimport {\n COMMON_UNIFORMS_GLSL,\n IMPORTED_IMAGERY_ALBEDO_GLSL,\n NOISE_GLSL,\n buildHeightGLSL,\n TERRAIN_HEIGHT_TEX_GLSL,\n NEAR_BAKE_BLOCK,\n CLIMATE_CACHE_BLOCK,\n INFINITE_CLIMATE_GLSL,\n fitNearBake,\n INFINITE_FIELD_CACHE_GLSL,\n TERRAIN_CLIMATE_CACHE_GLSL,\n MANUAL_SURFACE_WEIGHTS_GLSL,\n} from './terrainGLSL.js';\nimport { BIOME_GLSL } from './biomeGLSL.js';\nimport { generateStackGLSL } from './noise/noiseStackCodegen.js';\nimport { MINI_CUT_GLSL } from '../mini/miniCutGLSL.js';\nimport { MINI_BEVEL_GLSL } from '../mini/MiniBevel.js';\nimport { defaultLegacyStack, MAX_LAYERS } from './noise/NoiseStack.js';\nimport {\n PALETTE_UNIFORMS_GLSL,\n TERRAIN_COLOR_FUNCTIONS_GLSL,\n} from '../shaders/terrainColor.glsl.js';\nimport { TERRAIN_DETAIL_GLSL } from './TerrainDetailMaterial.js';\nimport {\n SURFACE_TEXTURE_UNIFORMS_GLSL,\n SURFACE_TEXTURE_FUNCTIONS_GLSL,\n SURFACE_TEXTURE_ROLE_COUNT,\n SURFACE_TEXTURE_ROWS,\n} from './surface/terrainSurfaceTextureGLSL.js';\nimport { createPaletteUniforms } from '../style/PaletteUniforms.js';\nimport { EARTH_PALETTE } from '../style/ColorPalette.js';\nimport { applyPlanetStyleToUniforms } from '../style/PaletteUniforms.js';\nimport { DEFAULT_PLANET_STYLE } from '../style/PlanetStyleConfig.js';\n\n// ============================================================================\n// Terrain shader. Everything happens on the GPU:\n// - vertex: world XZ -> procedural height, skirt drop on chunk borders\n// - fragment: finite-difference procedural normals, biome color from\n// palette uniforms + height / slope / moisture, sun + hemisphere lighting,\n// cavity AO, chunk grid overlay, LOD debug tint, exp2 fog.\n// ============================================================================\n\nconst MANUAL_ACTIVE_COMMON_SAMPLERS = new Set([\n // Manual surface weights reuse the two authoring mask units. Keeping a\n // second pair of samplers here is what pushed the hybrid shader over the\n // guaranteed WebGL2 fragment budget once the surface atlas was enabled.\n 'uPaintBiomeTexture',\n 'uPaintPropsTexture',\n 'uManualHeightTexture',\n 'uTileOccupancy',\n 'uDestructionTexture',\n]);\n\nconst MANUAL_COMMON_UNIFORMS_GLSL = COMMON_UNIFORMS_GLSL.replace(\n /^uniform sampler2D ([A-Za-z0-9_]+);.*$/gm,\n (line, name) => (MANUAL_ACTIVE_COMMON_SAMPLERS.has(name) ? line : ''),\n);\n\n// Standard terrain never reads the standalone Manual surface textures. Manual\n// weights are packed into the existing paint biome/props units (see\n// MANUAL_SURFACE_WEIGHTS_GLSL), so declaring the old pair here would reserve\n// two more fragment texture units for no reason.\nconst TERRAIN_COMMON_UNIFORMS_GLSL = COMMON_UNIFORMS_GLSL.replace(\n /^uniform sampler2D (uManualSurfaceTextureA|uManualSurfaceTextureB|uSplineAuxTexture);.*$/gm,\n '',\n);\n\n// Infinite terrain is rendered only after its clipmap program has been\n// prepared. Do not keep a procedural fallback in this material: that fallback\n// pulls every Studio authoring sampler into the live program and can take the\n// surface variant past MAX_TEXTURE_IMAGE_UNITS. The clipmap's ready flag still\n// drives the normal path; an unavailable sample is a temporary zero-height\n// value until the prepared cache is published.\nconst INFINITE_FIELD_CACHE_STRICT_GLSL = INFINITE_FIELD_CACHE_GLSL.replace(\n ' return heightAt(xz);\\n}',\n ' return 0.0;\\n}',\n);\n\nconst MANUAL_HEIGHT_GLSL = /* glsl */ `\n${MANUAL_SURFACE_WEIGHTS_GLSL}\n\nfloat manualHeightOffsetAt(vec2 xz) {\n if (uManualEnabled < 0.5) return 0.0;\n vec2 uv = (xz - uManualOrigin) / max(uManualSpan, vec2(1.0));\n if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) return 0.0;\n return textureLod(uManualHeightTexture, uv, 0.0).r;\n}\n\n// Manual Terrain owns its complete height and surface fields. These stubs keep\n// shared analysis/debug code source-compatible without activating the standard\n// paint, spline, import, erosion, Studio bake, or Infinite cache samplers.\nfloat paintHeightOffsetAt(vec2 xz) { return 0.0; }\nvec4 paintBiomeAt(vec2 xz) { return vec4(0.0); }\nfloat splineHeightOffsetAt(vec2 xz) { return 0.0; }\nvec4 splineMaskAt(vec2 xz) { return vec4(0.0); }\n\nvec2 destructionSampleAt(vec2 xz) {\n if (uDestructionEnabled < 0.5) return vec2(0.0);\n vec2 uv = (xz - uDestructionOrigin) / max(uDestructionSpan, vec2(1.0));\n if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0)))) return vec2(0.0);\n return textureLod(uDestructionTexture, uv, 0.0).rg;\n}\nfloat destructionOffsetAt(vec2 xz) { return destructionSampleAt(xz).r; }\nfloat destructionScorchAt(vec2 xz) { return destructionSampleAt(xz).g; }\n\nfloat heightAtWithClimate(vec2 xz, Climate climate) {\n return manualHeightOffsetAt(xz) + destructionOffsetAt(xz);\n}\n\nfloat heightAt(vec2 xz) {\n return manualHeightOffsetAt(xz) + destructionOffsetAt(xz);\n}\n\nfloat moistureAt(vec2 xz) {\n return climateAt(xz * uFrequency + uSeedOffset).moist;\n}\n\nfloat stackHeight2D(vec2 xz, Climate climate) {\n return clamp(manualHeightOffsetAt(xz) / max(uHeightScale, 1e-3), 0.0, 1.0);\n}\n`;\n\nconst MANUAL_TERRAIN_CACHE_GLSL = /* glsl */ `\nfloat terrainCachedHeightAt(vec2 xz) {\n return manualHeightOffsetAt(xz);\n}\n`;\n\n// Hybrid Manual projects are Tile-only. Keeping the Infinite World cache in\n// their shader would reserve three texture units that can never be used, and\n// the generated base + Manual height/surface maps would then exceed the common\n// WebGL limit of 16 active texture units. Evaluate the same world-coordinate\n// height function directly instead.\nconst HYBRID_TILE_TERRAIN_CACHE_GLSL = /* glsl */ `\nfloat terrainCachedHeightAt(vec2 xz) {\n return heightAt(xz);\n}\n`;\n\nconst MANUAL_TERRAIN_CLIMATE_GLSL = /* glsl */ `\nClimate terrainCachedClimateAt(vec2 xz) {\n return climateAt(xz * uFrequency + uSeedOffset);\n}\n`;\n\nconst DEFAULT_TERRAIN_GRAPH_COLOR_GLSL = /* glsl */ `\nvec3 applyTerrainGraphColor(vec3 fallback, vec2 xz, float h01, float slope, float detail, float moisture) {\n return fallback;\n}\n`;\n\nexport const TERRAIN_DETAIL_STUB_GLSL = /* glsl */ `\nuniform float uTerrainDetailDebug;\nuniform float uTerrainDetailNormalStrength;\nuniform float uTerrainDetailScale;\n\nstruct TerrainDetailResult {\n vec3 albedo;\n float detail;\n float fade;\n float rockMask;\n float shoreMask;\n};\n\nfloat terrainDetailQualityFactor() { return 0.0; }\nfloat terrainDetailEnabled() { return 0.0; }\nfloat terrainDetailRelief(vec3 worldPos, vec3 n, float scale) { return 0.5; }\n\nTerrainDetailResult applyTerrainDetailLayer(\n TerrainColorResult tc, Climate cl, BiomeWeights bw, vec3 worldPos,\n vec3 normalGeo, float hC, float hRel, float h01, float slope, float jitter\n) {\n TerrainDetailResult result;\n result.albedo = tc.albedo;\n result.detail = 0.5;\n result.fade = 0.0;\n result.rockMask = tc.rockBlend;\n result.shoreMask = 0.0;\n return result;\n}\n`;\n\nconst SURFACE_TEXTURE_STUB_GLSL = /* glsl */ `\nuniform float uSurfReveal;\nstruct SurfaceTexResult {\n vec3 albedo;\n vec3 normal;\n float ao;\n float rough;\n float amount;\n};\n\nSurfaceTexResult applySurfaceMaterials(\n vec3 baseAlbedo, vec3 n, vec3 baseNormal, vec3 nGeo, vec3 wpos, float dist,\n TerrainColorResult tc, Climate cl, BiomeWeights bw, float slope, float hRel,\n float h01, float detail, float jitter\n) {\n SurfaceTexResult result;\n result.albedo = baseAlbedo;\n result.normal = n;\n result.ao = 1.0;\n result.rough = 0.8;\n result.amount = 0.0;\n return result;\n}\n`;\n\nfunction resolveTerrainVariant(variant = 'full') {\n if (variant === 'manual-empty-base' || variant === 'manual-base') {\n return { name: variant, detail: true, surface: false, manual: true, manualSurface: false };\n }\n if (variant === 'manual-surface') {\n return { name: 'manual-surface', detail: true, surface: true, manual: true, manualSurface: true };\n }\n if (variant === 'hybrid-base') {\n return { name: 'hybrid-base', detail: true, surface: false, manual: false, manualSurface: false, tileOnly: true };\n }\n if (variant === 'manual-empty') {\n // With zero Manual paint coverage the atlas graph is a provable no-op.\n // Preserve the exact terrain/detail result and defer that large graph until\n // a surface-paint tool or loaded document actually needs it.\n return { name: 'manual-empty', detail: true, surface: false, manual: true, manualSurface: false };\n }\n if (variant === 'manual') {\n return { name: 'manual', detail: true, surface: true, manual: true, manualSurface: true };\n }\n if (variant === 'hybrid-surface') {\n return { name: 'hybrid-surface', detail: true, surface: true, manual: false, manualSurface: true, tileOnly: true };\n }\n if (variant === 'hybrid') {\n return { name: 'hybrid', detail: true, surface: true, manual: false, manualSurface: true, tileOnly: true };\n }\n if (variant === 'base') return { name: 'base', detail: true, surface: false, manualSurface: false };\n if (variant === 'detail') return { name: 'detail', detail: true, surface: false, manualSurface: false };\n if (variant === 'surface') return { name: 'surface', detail: true, surface: true, manualSurface: false };\n return { name: 'full', detail: true, surface: true, manual: false, manualSurface: false };\n}\n\n// Detail is a uniform-gated, page-backed part of every prepared terrain\n// program. A page becoming ready must not start another large ANGLE compile.\n// These pairs have identical GLSL and defines; only their readiness label\n// differs. Surface/PBR source changes still require a distinct program.\nexport function terrainShaderFamily(variant) {\n const families = {\n base: 'base', detail: 'base',\n surface: 'surface', full: 'surface',\n 'manual-empty-base': 'manual-empty', 'manual-base': 'manual-empty',\n 'manual-empty': 'manual-empty',\n 'manual-surface': 'manual-surface', manual: 'manual-surface',\n 'hybrid-base': 'hybrid-base',\n 'hybrid-surface': 'hybrid-surface', hybrid: 'hybrid-surface',\n };\n return families[variant] ?? variant;\n}\n\nconst resolveTerrainWorldMode = (worldMode) => (\n worldMode === 'infinite' || worldMode === 'shared' ? worldMode : 'studio'\n);\n\nconst buildTerrainCacheGLSL = (features, worldMode) => {\n if (features.manual) return MANUAL_TERRAIN_CACHE_GLSL;\n if (features.tileOnly || worldMode === 'studio') return HYBRID_TILE_TERRAIN_CACHE_GLSL;\n return worldMode === 'infinite'\n ? INFINITE_FIELD_CACHE_STRICT_GLSL\n : INFINITE_FIELD_CACHE_GLSL;\n};\n\nconst buildVertex = (heightGLSL, variant = 'full', requestedWorldMode = 'studio') => {\n const worldMode = resolveTerrainWorldMode(requestedWorldMode);\n const features = resolveTerrainVariant(variant);\n const manual = features.manual;\n const terrainCacheGLSL = buildTerrainCacheGLSL(features, worldMode);\n const commonUniformsGLSL = manual ? MANUAL_COMMON_UNIFORMS_GLSL : TERRAIN_COMMON_UNIFORMS_GLSL;\n const preview = variant === 'preview';\n return /* glsl */ `\n${commonUniformsGLSL}\n${TERRAIN_CLOUD_SHADOW_GLSL}\n${NOISE_GLSL}\n${BIOME_GLSL}\n${heightGLSL}\n${terrainCacheGLSL}\n\n${MINI_BEVEL_GLSL}\nuniform float uSkirtDepth;\nuniform float uPlinthBaseY;\nuniform float uWallThickness;\n${preview ? 'uniform float uEps;' : ''}\n\nattribute float aSkirt;\nattribute float aLod;\nattribute float aWall; // 1 on the dedicated circular radial-wall mesh, else 0\nattribute float aMorph; // 1 on the shared per-LOD chunk grids (absent = 0)\n#ifdef USE_INSTANCING\nattribute float aMorphK; // Infinite World: per-chunk temporal geomorph factor\n#endif\n\n// CDLOD geomorphing. A vertex of LOD L slides its odd grid indices onto the\n// even neighbours (= the LOD L+1 grid) as its distance goes from\n// uLodMorphStart[L] to uLodMorphEnd[L]. The engine places that band strictly\n// between the chunk switch distances (half a chunk diagonal inside each), so a\n// chunk is already exactly the coarser shape when it swaps geometry and still\n// exactly the finer shape when it swaps back: LOD changes never pop. The\n// metric matches the CPU selection: horizontal distance + camera height.\nuniform vec3 uLodMorphStart;\nuniform vec3 uLodMorphEnd;\nuniform vec4 uLodGridSegments; // quads per chunk side, LOD 0..3\n\nvec3 terrainMorphedGrid(vec3 grid) {\n if (aMorph < 0.5 || aLod > 2.5) return grid;\n float segments = aLod < 0.5 ? uLodGridSegments.x : aLod < 1.5 ? uLodGridSegments.y : uLodGridSegments.z;\n float coarser = aLod < 0.5 ? uLodGridSegments.y : aLod < 1.5 ? uLodGridSegments.z : uLodGridSegments.w;\n // only an exact 2:1 ladder collapses onto the next level's vertices\n if (abs(coarser * 2.0 - segments) > 0.5) return grid;\n #ifdef USE_INSTANCING\n // Infinite World chunks are instanced: their LOD changes are animated per\n // chunk by InfiniteWorld (temporal geomorph), settled chunks sit at 0.\n float morph = clamp(aMorphK, 0.0, 1.0);\n if (morph <= 0.0) return grid;\n #else\n vec4 base = modelMatrix * vec4(grid, 1.0);\n float viewDistance = length(vec3(base.x - cameraPosition.x, cameraPosition.y, base.z - cameraPosition.z));\n float morphStart = aLod < 0.5 ? uLodMorphStart.x : aLod < 1.5 ? uLodMorphStart.y : uLodMorphStart.z;\n float morphEnd = aLod < 0.5 ? uLodMorphEnd.x : aLod < 1.5 ? uLodMorphEnd.y : uLodMorphEnd.z;\n if (morphEnd <= morphStart) return grid;\n float morph = clamp((viewDistance - morphStart) / (morphEnd - morphStart), 0.0, 1.0);\n #endif\n vec2 index = floor(grid.xz * segments + 0.5);\n grid.xz -= mod(index, 2.0) / segments * morph;\n return grid;\n}\n\nvarying vec3 vWorldPos;\nvarying float vLod;\nvarying float vSkirt;\nvarying float vWall;\nvarying float vWallMesh;\nvarying float vCutRimH; // surface height above this column (Mini cut strata)\n${preview ? 'varying vec3 vTerrainPreviewNormal;' : ''}\n\nvoid main() {\n vec4 localPosition = vec4(terrainMorphedGrid(position), 1.0);\n #ifdef USE_INSTANCING\n localPosition = instanceMatrix * localPosition;\n #endif\n vec4 wp = modelMatrix * localPosition;\n vec2 squareXZ = wp.xz;\n if ((uMiniTerrainBevel > 0.0 || uMiniHexagon > 0.5) && uInfiniteMode < 0.5) {\n wp.xz = miniBevelPoint(wp.xz, uBoardHalf, uMiniTerrainBevel, uMiniBevelRounded);\n }\n float h = terrainCachedHeightAt(wp.xz);\n${preview ? /* glsl */ `\n // Smooth lighting for the lightweight Node preview. Two extra height\n // samples per vertex are much cheaper than evaluating the graph per pixel,\n // and interpolate cleanly across the visible terrain triangles.\n float normalEps = max(uEps, 0.001);\n float hNormalX = terrainCachedHeightAt(wp.xz + vec2(normalEps, 0.0));\n float hNormalZ = terrainCachedHeightAt(wp.xz + vec2(0.0, normalEps));\n vTerrainPreviewNormal = normalize(vec3(\n -(hNormalX - h) / normalEps,\n 1.0,\n -(hNormalZ - h) / normalEps\n ));\n` : ''}\n\n float skirt = aSkirt;\n float wall = 0.0; // outer-perimeter skirt -> plinth wall\n if (uInfiniteMode < 0.5) {\n if (uUseTiles > 0.5) {\n if (uTileShape > 0.5) {\n if (aWall > 0.5) {\n // Dedicated radial wall: top follows the same single analytic\n // terrain sample; the base ring drops to the plinth.\n wall = aSkirt;\n skirt = 0.0;\n } else {\n // Retain both terrain-toned crack covers. Either neighbour can be\n // the finer LOD, so fixed-side ownership can expose a T-junction.\n skirt = aSkirt;\n wall = 0.0;\n }\n } else {\n // multi-cell square assembly: only skirts on a cell edge facing empty\n // space become the plinth wall; shared seams stay continuous terrain.\n vec3 tw = tileWall(squareXZ);\n float onOuter = step(0.5, tw.x);\n skirt = aSkirt;\n wall = aSkirt * onOuter;\n skirt *= 1.0 - onOuter;\n wp.xz += tw.yz * (wall * uWallThickness);\n }\n } else {\n float bx = abs(squareXZ.x);\n float bz = abs(squareXZ.y);\n float onOuter = step(uBoardHalf - 1.0, bx) + step(uBoardHalf - 1.0, bz);\n // outer-edge skirt verts become the plinth wall; interior skirts unchanged\n wall = skirt * step(0.5, onOuter);\n skirt *= 1.0 - step(0.5, onOuter);\n // flare the wall base outward (away from the board) so it sits OUTSIDE the\n // water plane edge — no z-fighting with the water, and it leans over any\n // terrain edge that dips below the waterline so the side never shows through.\n vec2 outDir = vec2(step(uBoardHalf - 1.0, bx) * sign(wp.x),\n step(uBoardHalf - 1.0, bz) * sign(wp.z));\n wp.xz += outDir * (wall * uWallThickness);\n }\n }\n\n // interior skirt drops by uSkirtDepth; the perimeter wall drops all the way to\n // the plinth base so the terrain's own edge masks the under-the-map view at\n // whatever LOD the border chunks are rendered at.\n wp.y = mix(h - skirt * uSkirtDepth, uPlinthBaseY, wall);\n\n vWorldPos = wp.xyz;\n vLod = aLod;\n vSkirt = max(skirt, wall);\n vWall = wall;\n vWallMesh = aWall;\n vCutRimH = h;\n\n gl_Position = projectionMatrix * viewMatrix * wp;\n}\n`;\n};\n\nconst buildFragmentSource = (\n heightGLSL,\n graphColorGLSL = DEFAULT_TERRAIN_GRAPH_COLOR_GLSL,\n variant = 'full',\n requestedWorldMode = 'studio',\n) => {\n const worldMode = resolveTerrainWorldMode(requestedWorldMode);\n const features = resolveTerrainVariant(variant);\n const terrainCacheGLSL = buildTerrainCacheGLSL(features, worldMode);\n const commonUniformsGLSL = features.manual\n ? MANUAL_COMMON_UNIFORMS_GLSL\n : TERRAIN_COMMON_UNIFORMS_GLSL;\n const terrainHeightGLSL = features.manual || worldMode === 'infinite'\n ? ''\n : `${TERRAIN_HEIGHT_TEX_GLSL}\\n${NEAR_BAKE_BLOCK}${worldMode === 'studio' ? `\\n${CLIMATE_CACHE_BLOCK}` : ''}`;\n const climateCacheGLSL = features.manual\n ? MANUAL_TERRAIN_CLIMATE_GLSL\n : worldMode === 'shared' && !features.tileOnly\n ? TERRAIN_CLIMATE_CACHE_GLSL\n : worldMode === 'infinite' ? INFINITE_CLIMATE_GLSL : '';\n return /* glsl */ `\nprecision highp float;\n\n${commonUniformsGLSL}\n${TERRAIN_CLOUD_SHADOW_GLSL}\n${features.manual ? '' : features.manualSurface && features.tileOnly ? /* glsl */ `\n#ifdef SURFACE_ARRAYS\nvec3 applyImportedImageryAlbedo(vec3 baseAlbedo, vec2 xz) { return baseAlbedo; }\n#else\n${IMPORTED_IMAGERY_ALBEDO_GLSL}\n#endif\n` : IMPORTED_IMAGERY_ALBEDO_GLSL}\n${NOISE_GLSL}\n${BIOME_GLSL}\n${heightGLSL}\n${terrainHeightGLSL}\n${terrainCacheGLSL}\n${climateCacheGLSL}\n${PALETTE_UNIFORMS_GLSL}\n${TERRAIN_COLOR_FUNCTIONS_GLSL}\n${graphColorGLSL}\n${features.surface ? SURFACE_TEXTURE_UNIFORMS_GLSL : ''}\n${features.surface ? SURFACE_TEXTURE_FUNCTIONS_GLSL : SURFACE_TEXTURE_STUB_GLSL}\n${features.detail ? TERRAIN_DETAIL_GLSL : TERRAIN_DETAIL_STUB_GLSL}\n\nuniform float uNormalStrength;\nuniform float uAO;\nuniform float uGrid;\nuniform float uLodDebug;\nuniform float uMergeDebug; // 1 = tint merged-group / macro-proxy meshes\nuniform float uColorMode;\nuniform float uEps;\n${worldMode === 'studio' && !features.manual ? 'uniform int uTerrainNormalSampleCount;' : ''}\nuniform vec3 uPlinthColor;\n${MINI_CUT_GLSL}\nuniform float uAnalysisEnabled;\nuniform float uAnalysisMode;\nuniform float uAnalysisOpacity;\nuniform float uAnalysisMin;\nuniform float uAnalysisMax;\nuniform float uAnalysisThresholdA;\nuniform float uAnalysisThresholdB;\nuniform float uAnalysisContourSpacing;\nuniform float uAnalysisContourStrength;\n\n// Underwater caustics — focused sunlight projected on submerged terrain,\n// visible through the surface as well as when diving. World-XZ projection\n// keeps them seamless across chunks (Tile + Infinite).\n// Wave uniforms mirror the active water material so floor caustics drift with\n// the surface ripples (refraction through the wavy water plane).\nuniform float uCausticStrength; // user strength (0 = off)\nuniform float uCausticBlend; // projected caustic activation 0..1\nuniform float uCausticScale; // user scale multiplier\nuniform float uCausticSpeed; // user speed multiplier\nuniform vec3 uCausticColor;\nuniform float uCausticDepthFade; // depth (world units) over which caustics fade\nuniform float uCausticMinDepth; // no caustics closer than this to the surface\nuniform float uCausticMinDepthFalloff;\nuniform float uCausticWaterAnim;\nuniform float uCausticAnimSpeed;\nuniform float uCausticWaveSpeed;\nuniform float uCausticWaveScale;\nuniform float uCausticWaveStrength;\nuniform float uCausticLargeWaveStr;\nuniform float uCausticSmallWaveStr;\nuniform float uCausticRippleLegacy; // 1 = legacy water ripples, 0 = realistic\nuniform vec2 uCausticWaveDir;\n\nvarying vec3 vWorldPos;\nvarying float vLod;\nvarying float vSkirt;\nvarying float vWall;\nvarying float vWallMesh;\nvarying float vCutRimH;\n\nconst vec3 LOD_COLORS[4] = vec3[4](\n vec3(0.90, 0.28, 0.30),\n vec3(0.96, 0.65, 0.14),\n vec3(0.96, 0.85, 0.04),\n vec3(0.23, 0.51, 0.96)\n);\n\n// Merge-debug colour ramp keyed off aLod (4..8 = quadtree fold tier). Small\n// folds (2x2) read green, then yellow → orange → red → magenta as larger\n// blocks fold, so the nested quadtree structure is legible.\nvec3 mergeTierColor(float vlod) {\n float t = clamp(vlod - 4.0, 0.0, 4.0);\n if (t < 1.0) return mix(vec3(0.18, 0.95, 0.45), vec3(0.95, 0.95, 0.15), t);\n else if (t < 2.0) return mix(vec3(0.95, 0.95, 0.15), vec3(0.98, 0.55, 0.10), t - 1.0);\n else if (t < 3.0) return mix(vec3(0.98, 0.55, 0.10), vec3(0.95, 0.20, 0.20), t - 2.0);\n else return mix(vec3(0.95, 0.20, 0.20), vec3(0.95, 0.20, 0.95), t - 3.0);\n}\n\n// Moving Voronoi boundaries approximate the light focused between neighboring\n// wave lenses. The distance between the two closest sites produces connected\n// cells rather than isolated blobs or a repeating interference tile.\nfloat causticCellEdges(vec2 uv, float t, float defocus) {\n vec2 cell = floor(uv);\n vec2 f = fract(uv);\n float nearest = 10.0;\n float second = 10.0;\n vec2 nearestDelta = vec2(0.0);\n vec2 secondDelta = vec2(1.0);\n for (int y = -1; y <= 1 + uLoopGuard; y++) {\n for (int x = -1; x <= 1 + uLoopGuard; x++) {\n vec2 offset = vec2(float(x), float(y));\n vec2 id = cell + offset;\n vec2 phase = 6.28318 * vec2(\n hash12(id + vec2(17.3, 41.7)),\n hash12(id + vec2(83.1, 13.9))\n );\n vec2 site = offset + 0.5 + 0.30 * sin(phase + t * vec2(0.61, 0.79));\n vec2 delta = site - f;\n float d = dot(delta, delta);\n if (d < nearest) {\n second = nearest;\n secondDelta = nearestDelta;\n nearest = d;\n nearestDelta = delta;\n } else if (d < second) {\n second = d;\n secondDelta = delta;\n }\n }\n }\n // Distance to the bisector avoids broad white blobs when two sites approach.\n float edgeDistance = (second - nearest)\n / max(2.0 * length(secondDelta - nearestDelta), 0.001);\n float footprint = length(fwidth(uv)) * 0.45;\n float coreWidth = mix(0.014, 0.032, defocus);\n float haloWidth = mix(0.048, 0.080, defocus);\n float coreFilter = sqrt(coreWidth * coreWidth + footprint * footprint);\n float haloFilter = sqrt(haloWidth * haloWidth + footprint * footprint);\n float core = exp2(-2.0 * edgeDistance * edgeDistance / (coreFilter * coreFilter))\n * (0.014 / coreFilter);\n float halo = exp2(-2.0 * edgeDistance * edgeDistance / (haloFilter * haloFilter))\n * (0.048 / haloFilter);\n // Preserve light energy as lines become subpixel, then fade unresolved cells.\n // Widening a full-bright line with fwidth made distant water look milky.\n float resolved = 1.0 - smoothstep(0.16, 0.48, footprint);\n return (core * 0.82 + halo * 0.18) * resolved;\n}\n\n// Ripple height at the water surface — matches legacy or realistic water shaders.\nfloat causticRippleLayer(vec2 p, float t, float scale, float speed) {\n vec2 drift = uCausticWaveDir * t * speed;\n float h = vnoise(p * scale + drift);\n h += 0.45 * vnoise(p * scale * 2.4 - drift * 1.3);\n return h;\n}\n\nfloat causticRippleHeight(vec2 rp, float t) {\n float legacyH = vnoise(rp + vec2(t * 0.6, t * 0.45));\n legacyH += 0.5 * uCausticSmallWaveStr * vnoise(rp * 2.7 - vec2(t * 0.8, t * 0.3));\n float realisticH =\n causticRippleLayer(rp, t, 1.0, uCausticWaveSpeed) * uCausticLargeWaveStr\n + causticRippleLayer(rp, t, 2.6, uCausticWaveSpeed * 1.3) * uCausticSmallWaveStr;\n return mix(realisticH, legacyH, step(0.5, uCausticRippleLegacy));\n}\n\n// Surface slope refracts sunlight — shift caustic sampling to follow the waves.\nvec2 causticSurfaceRefraction(vec2 xz, float t) {\n vec2 rp = xz * 0.055 * uCausticWaveScale;\n float e = (uCausticRippleLegacy > 0.5)\n ? 1.6\n : (1.4 / max(uCausticWaveScale, 0.2));\n float r0 = causticRippleHeight(rp, t);\n float rX = causticRippleHeight(rp + vec2(e * 0.055, 0.0), t);\n float rZ = causticRippleHeight(rp + vec2(0.0, e * 0.055), t);\n return vec2(-(rX - r0), -(rZ - r0)) * uCausticWaveStrength;\n}\n\n// Bend the cell boundaries with broad surface waves, then overlay a weaker,\n// smaller lens network. Both layers stay anchored in world space.\nfloat causticPattern(vec2 xz, float t, vec2 refr, float depthSpread, float defocus) {\n // About eleven world units per cell at the default scale (formerly 33).\n float s = 0.09 / max(uCausticScale, 0.05);\n vec2 uv = (xz + refr * 2.8 * depthSpread) * s;\n vec2 warp = vec2(\n vnoise(uv * 0.78 + vec2(t * 0.035, 7.1)),\n vnoise(uv * 0.78 + vec2(4.3, 2.1 - t * 0.029))\n ) - 0.5;\n vec2 bent = uv + warp * 0.72;\n float broad = causticCellEdges(bent, t * 0.68, defocus);\n float fine = causticCellEdges(ROT2 * (bent * 1.57) + vec2(4.7, 1.3), t * 0.91, defocus);\n float lightVariation = vnoise(uv * 0.23 + vec2(13.2, 6.8));\n float filamentVariation = vnoise(bent * 2.1 + vec2(t * 0.08, -t * 0.05));\n float focus = smoothstep(0.20, 0.78, filamentVariation);\n float illumination = broad * (0.18 + 1.22 * focus) + fine * 0.16\n + broad * fine * 0.24;\n return clamp(illumination * (0.48 + 0.65 * lightVariation), 0.0, 1.0);\n}\n\n// Project caustics onto the submerged, upward-facing sea floor. World-XZ space\n// → seamless across chunks (Tile + Infinite). Modulated by sun lighting and\n// water depth so it genuinely sits in the environment, not on the lens.\nvec3 applyTerrainCaustics(vec3 col, vec2 xz, float visibleHeight, vec3 nGeo, vec3 lightN) {\n float amt = uCausticStrength * uCausticBlend;\n if (amt < 0.001) return col;\n\n // Structural skirt and perimeter-wall geometry is not sea floor. Use the\n // visible faceted height because the analytic/baked height can be submerged\n // while a coarse rendered triangle is visibly above the water.\n if (vSkirt > 0.0001 || vWallMesh > 0.5) return col;\n float below = uSeaLevel - visibleHeight; // >0 only when the rendered surface is submerged\n if (below <= 0.0) return col;\n\n // shallow terrain near the shoreline catches the most light; deep fades out\n float depthFade = 1.0 - clamp(below / max(uCausticDepthFade, 1.0), 0.0, 1.0);\n depthFade *= depthFade;\n // upward-facing surfaces catch the light; vertical cliffs stay dark\n float upFace = clamp(nGeo.y * 1.1, 0.0, 1.0);\n upFace *= upFace;\n // sunlight drives the caustics — facets toward the sun are brightest, and the\n // whole effect dims when the sun is low (no light = no caustics)\n float sunFace = max(dot(lightN, uSunDir), 0.0);\n float sunUp = clamp(uSunDir.y * 2.0, 0.0, 1.0);\n float light = (0.35 + 0.65 * sunFace) * sunUp;\n\n float t = uTime * uCausticWaterAnim * uCausticAnimSpeed * uCausticSpeed;\n vec2 refr = causticSurfaceRefraction(xz, t);\n float depthSpread = 1.0 + below * 0.012;\n float defocus = smoothstep(4.0, max(uCausticDepthFade * 0.65, 5.0), below);\n float c = causticPattern(xz, t, refr, depthSpread, defocus);\n\n // Focused light reveals the floor's texture; a small scattered contribution\n // keeps dark rock readable without painting opaque white lines over it.\n float illumination = c * amt * depthFade * upFace * light;\n return col + (col * 1.65 + vec3(0.12)) * uCausticColor * illumination;\n}\n\nvec3 applyTerrainDetailNormal2D(vec3 n, vec3 nGeo, vec3 worldPos, float fade, float rockMask, float shoreMask) {\n float strength = uTerrainDetailNormalStrength * fade * (0.45 + 0.55 * terrainDetailQualityFactor());\n if (strength <= 0.0001) return n;\n float scale = uTerrainDetailScale * mix(0.55, 1.25, terrainDetailQualityFactor());\n float e = max(0.45, 0.55 / max(scale, 0.0001));\n float c = terrainDetailRelief(worldPos, nGeo, scale);\n float dx = terrainDetailRelief(worldPos + vec3(e, 0.0, 0.0), nGeo, scale) - c;\n float dz = terrainDetailRelief(worldPos + vec3(0.0, 0.0, e), nGeo, scale) - c;\n float matStrength = strength * (0.55 + rockMask * 1.05 + shoreMask * 0.25);\n vec3 detailN = normalize(n + vec3(-dx * matStrength * 5.5, 0.0, -dz * matStrength * 5.5));\n return normalize(mix(n, detailN, terrainDetailEnabled()));\n}\n\nvoid main() {\n vec2 xz = vWorldPos.xz;\n\n // Circular assemblies still use square chunk meshes. Remove every chunk\n // fragment outside the disk so the original board cannot show through at zero\n // height. The radial wall (vWallMesh) sits ON the perimeter, so it is exempt.\n if (uInfiniteMode < 0.5 && uTileShape > 0.5 && vWallMesh < 0.5 && tileOccupiedAt(xz) < 0.5) discard;\n\n // Procedural terrain shading uses the same climate function as terrain\n // formation. Tile mode reads its four slow fields from the half-float,\n // generation-matched climate bake (exact at texel centres; the old 8-bit\n // cache quantized biome thresholds into visible colour steps) and keeps the\n // region jitter live; without the bake the exact live function runs.\n${features.manual ? /* glsl */ `\n Climate cl = terrainCachedClimateAt(xz);\n` : /* glsl */ `\n${worldMode === 'infinite' ? /* glsl */ `\n float infiniteFieldAvailable = 0.0;\n vec4 infiniteField = infiniteFieldSampleAt(xz, infiniteFieldAvailable);\n Climate cl = terrainInfiniteClimateAt(xz, infiniteField, infiniteFieldAvailable);\n` : /* glsl */ `\n#ifdef TERRAIN_CLIMATE_CACHE\n Climate cl = terrainStudioClimateAt(xz);\n#else\n Climate cl = climateAt(xz * uFrequency + uSeedOffset);\n#endif\n`}\n`}\n BiomeWeights bw = biomeWeightsAt(cl);\n vec4 paintedBiome = ${features.manual ? 'vec4(0.0)' : 'paintBiomeAt(xz)'};\n vec4 splineMask = ${features.manual ? 'vec4(0.0)' : 'splineMaskAt(xz)'};\n bw.desert = clamp(max(bw.desert, paintedBiome.r), 0.0, 1.0);\n bw.canyon = clamp(max(bw.canyon, max(paintedBiome.g, splineMask.r * (1.0 - splineMask.b))), 0.0, 1.0);\n bw.wetland = clamp(max(bw.wetland, max(paintedBiome.b, splineMask.r * splineMask.b)), 0.0, 1.0);\n bw.mountains = clamp(max(bw.mountains, paintedBiome.a), 0.0, 1.0);\n${features.manual ? '' : /* glsl */ `\n if (uInfiniteMode < 0.5 && uImportBiomeMode > 1.5) {\n float b = importedMapValue(uImportBiomeTex, tileUvAt(xz));\n BiomeWeights importedBw;\n importedBw.desert = 1.0 - smoothstep(0.18, 0.32, b);\n importedBw.canyon = smoothstep(0.22, 0.42, b) * (1.0 - smoothstep(0.43, 0.58, b));\n importedBw.wetland = smoothstep(0.44, 0.60, b) * (1.0 - smoothstep(0.62, 0.78, b));\n importedBw.mountains = smoothstep(0.66, 0.86, b);\n if (uImportBiomeMode > 2.5) {\n bw.desert = mix(bw.desert, importedBw.desert, uImportBiomeBlend);\n bw.canyon = mix(bw.canyon, importedBw.canyon, uImportBiomeBlend);\n bw.wetland = mix(bw.wetland, importedBw.wetland, uImportBiomeBlend);\n bw.mountains = mix(bw.mountains, importedBw.mountains, uImportBiomeBlend);\n } else {\n bw = importedBw;\n }\n }\n`}\n\n float eps = uEps;\n float hC, hX, hZ;\n vec3 nGeo;\n${features.manual ? /* glsl */ `\n hC = terrainCachedHeightAt(xz);\n hX = terrainCachedHeightAt(xz + vec2(eps, 0.0));\n hZ = terrainCachedHeightAt(xz + vec2(0.0, eps));\n nGeo = normalize(vec3(-(hX - hC) / eps, 1.0, -(hZ - hC) / eps));\n` : worldMode === 'infinite' ? /* glsl */ `\n // same field sample as the climate above (terrainCachedHeightAt, strict)\n if (infiniteFieldAvailable > 0.5) hC = infiniteField.r * uHeightScale;\n else hC = 0.0;\n float normalDistance = length(cameraPosition - vWorldPos);\n bool farInfiniteNormal = vLod > 1.5 || normalDistance > max(uChunkSize * 7.0, 900.0);\n // Screen derivatives are taken in uniform control flow: a gradient inside the\n // divergent branch would make the compiler flatten it and always pay for the\n // two neighbour samples below.\n vec3 faceNormal = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n if (farInfiniteNormal) {\n hX = hC;\n hZ = hC;\n nGeo = faceNormal;\n if (nGeo.y < 0.0) nGeo = -nGeo;\n } else {\n hX = terrainCachedHeightAt(xz + vec2(eps, 0.0));\n hZ = terrainCachedHeightAt(xz + vec2(0.0, eps));\n nGeo = normalize(vec3(-(hX - hC) / eps, 1.0, -(hZ - hC) / eps));\n }\n` : /* glsl */ `\n // Live/baked hybrid (see terrainBakeBlendWeight). Near the camera the exact\n // procedural field is evaluated per pixel; once a pixel spans about a bake\n // texel the packed bake (exact values at texel centres) replaces it. Both\n // are real branches: nothing in either path uses implicit derivatives.\n hC = 0.0;\n hX = 0.0;\n hZ = 0.0;\n nGeo = vec3(0.0, 1.0, 0.0);\n float viewDistanceForBake = length(cameraPosition - vWorldPos);\n float bakeWeight = terrainBakeWeight(viewDistanceForBake);\n // the near level serves what the board bake would magnify\n float nearWeight = 0.0;\n#ifdef TERRAIN_NEAR_BAKE\n if (bakeWeight < 1.0 && uUseTerrainHeightTex > 0.5 && uInfiniteMode < 0.5) {\n nearWeight = terrainNearBakeBlendWeight(xz, viewDistanceForBake) * (1.0 - bakeWeight);\n }\n#endif\n float bakedWeight = bakeWeight + nearWeight;\n${worldMode === 'studio' ? '' : /* glsl */ `\n vec3 faceNormal = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos)));\n`}\n if (bakedWeight < 1.0) {\n${worldMode === 'studio' ? /* glsl */ `\n // Keep one copy of the procedural field in the driver program. Three\n // separate calls inline the entire noise/biome graph three times in ANGLE.\n // This uniform is always 3; only the outer sampling loop is dynamic, while\n // the noise octave loops retain their compiler-safe constant bounds.\n vec3 samples = vec3(0.0);\n for (int sampleIndex = 0; sampleIndex < uTerrainNormalSampleCount; sampleIndex++) {\n vec2 offset = sampleIndex == 1 ? vec2(eps, 0.0) : sampleIndex == 2 ? vec2(0.0, eps) : vec2(0.0);\n samples[sampleIndex] = terrainCachedHeightAt(xz + offset);\n }\n hC = samples.x; hX = samples.y; hZ = samples.z;\n nGeo = normalize(vec3(-(hX - hC) / eps, 1.0, -(hZ - hC) / eps));\n` : /* glsl */ `\n hC = terrainCachedHeightAt(xz);\n float normalDistance = length(cameraPosition - vWorldPos);\n bool farInfiniteNormal = uInfiniteMode > 0.5\n && (vLod > 1.5 || normalDistance > max(uChunkSize * 7.0, 900.0));\n if (farInfiniteNormal) {\n hX = hC;\n hZ = hC;\n nGeo = faceNormal;\n if (nGeo.y < 0.0) nGeo = -nGeo;\n } else {\n hX = terrainCachedHeightAt(xz + vec2(eps, 0.0));\n hZ = terrainCachedHeightAt(xz + vec2(0.0, eps));\n nGeo = normalize(vec3(-(hX - hC) / eps, 1.0, -(hZ - hC) / eps));\n }\n`}\n }\n if (bakedWeight > 0.0) {\n vec4 bakeTexel;\n#ifdef TERRAIN_NEAR_BAKE\n if (nearWeight > 0.0) {\n vec4 nearTexel = textureLod(uNearBakeTex, nearBakedUvAt(xz), 0.0);\n if (bakeWeight > 0.0) {\n vec4 boardTexel = textureLod(uTerrainHeightTex, bakedUvAt(xz), 0.0);\n bakeTexel = (boardTexel * bakeWeight + nearTexel * nearWeight) / bakedWeight;\n } else {\n bakeTexel = nearTexel;\n }\n } else {\n bakeTexel = textureLod(uTerrainHeightTex, bakedUvAt(xz), 0.0);\n }\n#else\n bakeTexel = textureLod(uTerrainHeightTex, bakedUvAt(xz), 0.0);\n#endif\n float liveCurvature = ((hX + hZ) * 0.5 - hC) / eps;\n float curvature = mix(liveCurvature, bakeTexel.b, bakedWeight);\n hC = mix(hC, bakeTexel.a * uHeightScale, bakedWeight);\n nGeo = normalize(mix(nGeo, bakedNormalFromTexel(bakeTexel), bakedWeight));\n // Neighbour heights only feed the curvature terms (concavity AO, ridge\n // accent, curvature analysis); rebuild them from the blended curvature.\n hX = hC + curvature * eps;\n hZ = hX;\n }\n`}\n\n if (uTileDebugView > 0.5) {\n float h01 = clamp(hC / max(uHeightScale, 1e-3), 0.0, 1.0);\n if (uTileDebugView < 1.5) {\n float n = stackHeight2D(xz, cl);\n${features.manual ? '' : /* glsl */ `\n if (uInfiniteMode < 0.5 && uImportNoiseMode > 1.5) {\n float importedNoise = importedMapValue(uImportNoiseTex, tileUvAt(xz)) * uAmplitude;\n n = (uImportNoiseMode > 2.5) ? mix(n, importedNoise, uImportNoiseBlend) : importedNoise;\n }\n`}\n gl_FragColor = vec4(vec3(clamp(n, 0.0, 1.0)), 1.0);\n } else if (uTileDebugView < 2.5) {\n gl_FragColor = vec4(vec3(h01), 1.0);\n } else {\n vec3 dbg = terrainBiomeDebugColor(bw, h01);\n gl_FragColor = vec4(dbg, 1.0);\n }\n return;\n }\n\n if (uColorMode > 0.5) {\n // mode 3 packs the ACTUAL rendered (faceted) surface height — the\n // interpolated displaced vertex Y — so prop placement matches the visible\n // LOD mesh exactly, not the smooth analytic field that floats above crests.\n float hSrc = (uColorMode > 2.5) ? vWorldPos.y : hC;\n float h01 = clamp(hSrc / max(uHeightScale, 1e-3), 0.0, 1.0);\n if (uColorMode > 1.5) {\n // modes 2 & 3: 16-bit height packed into RG (collision / prop surface tile)\n float hi = floor(h01 * 255.0) / 255.0;\n float lo = fract(h01 * 255.0);\n gl_FragColor = vec4(hi, lo, 0.0, 1.0);\n } else {\n // mode 1: 8-bit grayscale (heightmap export)\n gl_FragColor = vec4(vec3(h01), 1.0);\n }\n return;\n }\n\n // perimeter plinth wall: flat plinth colour (with fog), no terrain shading.\n // Placed after the export/debug early-outs so heightmap/minimap stay clean.\n // vWall interpolates 0 (surface rim vertex) -> 1 (skirt vertex at the base),\n // so a small threshold colours the whole wall, leaving only a hairline of\n // terrain colour at the rim where it meets the surface (a natural transition).\n if (vWall > 0.02) {\n if (uMiniCut > 0.5) {\n gl_FragColor = vec4(miniCutShade(vWorldPos, vCutRimH, uSeaLevel, uSunDir), 1.0);\n return;\n }\n float wd = length(cameraPosition - vWorldPos);\n float wfog = 1.0 - exp(-uFogDensity * uFogDensity * wd * wd);\n vec3 wcol = mix(uPlinthColor, uFogColor, clamp(wfog, 0.0, 1.0));\n gl_FragColor = vec4(wcol, 1.0);\n return;\n }\n\n vec3 n = normalize(vec3(nGeo.x * uNormalStrength, 1.0, nGeo.z * uNormalStrength));\n vec3 surfaceBaseNormal = n;\n\n float slope = 1.0 - nGeo.y;\n // Wet/dry color classification follows the surface actually rasterized by\n // the active LOD mesh. Using the higher-resolution bake here colored visible\n // terrain blue when its interpolated triangle sat above the water plane.\n float hRel = vWorldPos.y - uSeaLevel;\n float h01 = hC / max(uHeightScale, 1e-3);\n\n if (uBiomeDebug > 0.5) {\n vec3 dbg = terrainBiomeDebugColor(bw, h01);\n float shade = 0.55 + 0.45 * max(dot(n, uSunDir), 0.0);\n gl_FragColor = vec4(pow(dbg * shade, vec3(1.0 / 2.2)), 1.0);\n return;\n }\n\n float jitter = (cl.region - 0.5) * 0.8 + (vnoise(xz * 0.045 + uSeedOffset) - 0.5) * 0.6;\n float detail = vnoise(xz * 0.35 + uSeedOffset.yx);\n\n TerrainColorResult tc = computeTerrainAlbedo(cl, bw, hC, hRel, h01, slope, detail, jitter, vnoise(xz * 0.9));\n tc.albedo = applyTerrainGraphColor(tc.albedo, xz, clamp(h01, 0.0, 1.0), slope, detail, cl.moist);\n TerrainDetailResult td = applyTerrainDetailLayer(tc, cl, bw, vWorldPos, nGeo, hC, hRel, h01, slope, jitter);\n n = applyTerrainDetailNormal2D(n, nGeo, vWorldPos, td.fade, td.rockMask, td.shoreMask);\n\n if (uTerrainDetailDebug > 0.5) {\n vec3 dbg = vec3(0.0);\n if (uTerrainDetailDebug < 1.5) {\n dbg = vec3(clamp(slope * 2.4, 0.0, 1.0));\n } else if (uTerrainDetailDebug < 2.5) {\n dbg = mix(vec3(0.08, 0.10, 0.12), vec3(0.70, 0.72, 0.68), td.rockMask);\n } else if (uTerrainDetailDebug < 3.5) {\n dbg = mix(vec3(0.04, 0.08, 0.10), vec3(0.82, 0.68, 0.40), td.shoreMask);\n } else if (uTerrainDetailDebug < 4.5) {\n dbg = vec3(td.fade);\n } else if (uTerrainDetailDebug < 5.5) {\n dbg = vec3(td.detail);\n } else if (uTerrainDetailDebug < 6.5) {\n dbg = td.albedo;\n } else {\n dbg = n * 0.5 + 0.5;\n }\n gl_FragColor = vec4(pow(max(dbg, vec3(0.0)), vec3(1.0 / 2.2)), 1.0);\n return;\n }\n\n // Surface textures: replace / tint the procedural biome colour with real\n // material textures (triplanar, blended by the same signals). No-op cost when\n // the mode is off or the camera is far (uSurfMode/uSurfAmount uniform branch).\n float dist = length(cameraPosition - vWorldPos);\n SurfaceTexResult surf = applySurfaceMaterials(\n td.albedo, n, surfaceBaseNormal, nGeo, vWorldPos, dist, tc, cl, bw, slope, hRel, h01, detail, jitter\n );\n surf.albedo = mix(td.albedo, surf.albedo, uSurfReveal);\n surf.normal = normalize(mix(n, surf.normal, uSurfReveal));\n surf.ao = mix(1.0, surf.ao, uSurfReveal);\n surf.rough = mix(0.8, surf.rough, uSurfReveal);\n surf.amount *= uSurfReveal;\n td.albedo = surf.albedo;\n n = surf.normal;\n\n${features.manual ? '' : /* glsl */ `\n // Geo-aligned OpenTopoMap (or file) imagery — same UV region as the real-world\n // height import. Applied after surface materials so the map reads as true albedo.\n td.albedo = applyImportedImageryAlbedo(td.albedo, xz);\n`}\n\n float destructionScorch = destructionScorchAt(xz);\n if (destructionScorch > 0.001) {\n vec3 charColor = vec3(0.055, 0.036, 0.025);\n td.albedo = mix(td.albedo, charColor, destructionScorch * 0.84);\n surf.rough = mix(surf.rough, 1.0, destructionScorch * 0.72);\n }\n\n float concave = clamp(((hX + hZ) * 0.5 - hC) / (eps * 0.9), 0.0, 1.0);\n float valley = 1.0 - smoothstep(0.0, uHeightScale * 0.55, hC);\n float ao = (1.0 - uAO * (concave * 0.45 + valley * 0.22)) * surf.ao;\n ao = applyRidgeAccent(ao, (hC - (hX + hZ) * 0.5) / (eps * 0.9));\n\n vec3 viewDir = normalize(cameraPosition - vWorldPos);\n float proceduralSheen = 1.0 - clamp(surf.amount, 0.0, 1.0);\n vec3 col = terrainLighting(\n td.albedo, n, uSunDir, ao,\n tc.snow, tc.sandBand, hRel, tc.flatness, bw.wetland,\n proceduralSheen, viewDir\n );\n\n // Attenuate direct sun while preserving sky light and ground bounce.\n col -= td.albedo * uTerrainSunCol * uTerrainSunIntensity\n * max(dot(n, uSunDir), 0.0) * ao * terrainCloudShadow(vWorldPos);\n\n // sampled roughness -> subtle view-dependent sheen (smoother materials glint)\n if (surf.amount > 0.001) {\n float ssp = pow(max(dot(reflect(-uSunDir, n), viewDir), 0.0), 24.0);\n float gloss = 1.0 - clamp(surf.rough, 0.0, 1.0);\n col += ssp * gloss * gloss * surf.amount * 0.08 * max(uSunDir.y, 0.0);\n }\n\n // Caustics illuminate submerged terrain; dry fragments return immediately.\n col = applyTerrainCaustics(col, xz, vWorldPos.y, nGeo, n);\n\n // Analysis is a lightweight branch in the existing terrain pass. It reads\n // the final height function, so paint, erosion and spline edits agree.\n if (uAnalysisEnabled > 0.5) {\n vec3 analysis = vec3(0.0);\n float rangeT = clamp((hC - uAnalysisMin) / max(uAnalysisMax - uAnalysisMin, 0.001), 0.0, 1.0);\n if (uAnalysisMode < 1.5) {\n analysis = mix(vec3(0.05, 0.17, 0.42), vec3(0.92, 0.72, 0.24), rangeT);\n float contour = abs(fract(hC / max(uAnalysisContourSpacing, 1.0)) - .5);\n analysis = mix(analysis, vec3(0.04), (1.0 - smoothstep(.0, .055, contour)) * uAnalysisContourStrength);\n } else if (uAnalysisMode < 2.5) {\n float deg = acos(clamp(nGeo.y, -1.0, 1.0)) * 57.2958;\n analysis = deg < uAnalysisThresholdA ? mix(vec3(.07,.35,.16), vec3(.75,.78,.16), deg / max(uAnalysisThresholdA, 1.0)) : mix(vec3(.92,.58,.10), vec3(.70,.08,.08), clamp((deg-uAnalysisThresholdA)/max(uAnalysisThresholdB-uAnalysisThresholdA,1.0),0.,1.));\n } else if (uAnalysisMode < 3.5) analysis = nGeo * .5 + .5;\n else if (uAnalysisMode < 4.5) { float curv = clamp(((hX + hZ) * .5 - hC) / max(eps * 4.0, .001), -.5, .5); analysis = curv > 0. ? mix(vec3(.35), vec3(.95,.65,.18), curv*2.) : mix(vec3(.35), vec3(.12,.45,.95), -curv*2.); }\n else if (uAnalysisMode < 5.5) { float depth = max(uSeaLevel - hC, 0.0); analysis = mix(vec3(.08,.35,.55), vec3(.01,.02,.18), clamp(depth / max(uAnalysisMax, 1.0), 0., 1.)); }\n else if (uAnalysisMode < 6.5) analysis = terrainBiomeDebugColor(bw, h01);\n else { float p = abs(paintHeightOffsetAt(xz)); float sp = abs(splineHeightOffsetAt(xz)); analysis = vec3(clamp(p/20.,0.,1.), clamp(sp/20.,0.,1.), splineMaskAt(xz).r); }\n col = mix(col, analysis, uAnalysisOpacity);\n }\n\n if (uGrid > 0.001) {\n vec2 gw = fwidth(xz) + 1e-5;\n vec2 gp = abs(fract(xz / uChunkSize - 0.5) - 0.5) * uChunkSize / gw;\n float line = 1.0 - min(min(gp.x, gp.y), 1.0);\n float gridFade = smoothstep(420.0, 60.0, length(cameraPosition - vWorldPos) / 8.0);\n float gridMul = 1.0;\n if (uInfiniteMode < 0.5 && uUseTiles > 0.5) gridMul = 1.0 - tileInteriorSeam(xz);\n // Grid lines recolour over folded terrain so the chunk grid also shows\n // which chunks have merged, ramped by fold level (green = small fold →\n // magenta = whole board); default blue over live detailed chunks.\n vec3 gridCol = vLod > 3.5 ? mergeTierColor(vLod) : vec3(0.45, 0.80, 0.95);\n col = mix(col, gridCol, line * uGrid * 0.22 * (0.35 + 0.65 * gridFade) * gridMul);\n }\n\n if (uLodDebug > 0.5) {\n int li = int(clamp(vLod, 0.0, 3.0) + 0.5);\n col = mix(col, LOD_COLORS[li], 0.55);\n }\n\n // Merge debug: tint folded terrain by fold level (green = small 2x2 fold →\n // magenta = whole board). Detailed chunks stay untouched so folds stand out.\n if (uMergeDebug > 0.5 && vLod > 3.5) {\n col = mix(col, mergeTierColor(vLod), 0.55);\n }\n\n // Crack-cover skirts retain terrain colour in every production mode.\n // Dark flaps turn ordinary LOD covers into the long grid lines users saw.\n float skirtDarken = 0.0;\n col *= 1.0 - skirtDarken;\n\n float fogF = 1.0 - exp(-uFogDensity * uFogDensity * dist * dist);\n col = mix(col, uFogColor, clamp(fogF, 0.0, 1.0));\n\n col = pow(col, vec3(1.0 / 2.2));\n gl_FragColor = vec4(col, 1.0);\n}\n`;\n};\n\n// Variants already at the 16-sampler budget compile without the near-bake level.\nconst buildFragment = (...args) => fitNearBake(buildFragmentSource(...args));\n\n// Minimal boot fragment: height + a simple height/slope-banded colour + sun +\n// fog only. It skips the palette/colour, surface-texture, terrain-detail and\n// caustic blocks — the dominant cost of the full fragment's synchronous\n// GLSL→HLSL translation on Windows/ANGLE (the multi-second tab freeze). Used\n// for the first Tile paint; the live material's source is then swapped in place\n// (rebuildTerrainShaderSource) for an instant program-cache hit.\nconst buildMinimalFragment = (graphColorGLSL = DEFAULT_TERRAIN_GRAPH_COLOR_GLSL) => /* glsl */ `\nprecision highp float;\n\n${COMMON_UNIFORMS_GLSL}\n${TERRAIN_CLOUD_SHADOW_GLSL}\n${TERRAIN_HEIGHT_TEX_GLSL}\n${NEAR_BAKE_BLOCK}\n${PALETTE_UNIFORMS_GLSL}\n${graphColorGLSL}\n\nuniform float uColorMode;\nuniform float uEps;\nuniform float uNormalStrength;\nuniform vec3 uPlinthColor;\n${MINI_CUT_GLSL}\n\nvarying vec3 vWorldPos;\nvarying float vSkirt;\nvarying float vWall;\nvarying float vWallMesh;\nvarying float vCutRimH;\nvarying vec3 vTerrainPreviewNormal;\n\n// The preview has no live per-pixel field: wherever the camera-centred near\n// level covers a pixel finer than the board bake, prefer it (it holds the\n// same exact values at twice the density).\nvec4 previewBakedTexel(vec2 xz) {\n vec4 texel = textureLod(uTerrainHeightTex, bakedUvAt(xz), 0.0);\n if (uUseNearBake > 0.5) {\n vec2 nearUv = nearBakedUvAt(xz);\n vec2 border = min(nearUv, 1.0 - nearUv) * uNearBakeSpan;\n float inside = clamp(min(border.x, border.y) / max(uNearBakeTexelWorld * 16.0, 1e-4), 0.0, 1.0);\n // a pixel wider than one board texel is better served by the board bake\n float footprint = length(cameraPosition - vWorldPos) * uPixelWorldPerDist / max(uNearBakeTexelWorld, 1e-4);\n float nearWeight = inside * (1.0 - smoothstep(1.0, 2.0, footprint));\n if (nearWeight > 0.0) texel = mix(texel, textureLod(uNearBakeTex, nearUv, 0.0), nearWeight);\n }\n return texel;\n}\n\nvoid main() {\n vec2 xz = vWorldPos.xz;\n\n if (uInfiniteMode < 0.5 && uTileShape > 0.5 && vWallMesh < 0.5 && tileOccupiedAt(xz) < 0.5) discard;\n\n float hC;\n vec3 nGeo;\n if (uInfiniteMode < 0.5 && uUseTerrainHeightTex > 0.5) {\n vec4 packedHeightNormal = previewBakedTexel(xz);\n hC = packedHeightNormal.a * uHeightScale;\n nGeo = bakedNormalFromTexel(packedHeightNormal);\n } else\n {\n hC = vWorldPos.y;\n // Until the bake of the current graph completes (just after a node edit,\n // or during boot) use the smoothly interpolated normal prepared by the\n // preview vertex shader instead of treating every slope as horizontal.\n nGeo = normalize(vTerrainPreviewNormal);\n }\n\n // keep the height-packing export/sampler modes correct while the boot\n // material is live (prop placement / collision tiles may render early)\n if (uColorMode > 0.5) {\n float hp = clamp(((uColorMode > 2.5) ? vWorldPos.y : hC) / max(uHeightScale, 1e-3), 0.0, 1.0);\n if (uColorMode > 1.5) {\n float hi = floor(hp * 255.0) / 255.0;\n float lo = fract(hp * 255.0);\n gl_FragColor = vec4(hi, lo, 0.0, 1.0);\n } else {\n gl_FragColor = vec4(vec3(hp), 1.0);\n }\n return;\n }\n\n float dist = length(cameraPosition - vWorldPos);\n\n if (vWall > 0.02) {\n if (uMiniCut > 0.5) {\n gl_FragColor = vec4(miniCutShade(vWorldPos, vCutRimH, uSeaLevel, uSunDir), 1.0);\n return;\n }\n float wfog = 1.0 - exp(-uFogDensity * uFogDensity * dist * dist);\n gl_FragColor = vec4(mix(uPlinthColor, uFogColor, clamp(wfog, 0.0, 1.0)), 1.0);\n return;\n }\n\n float slope = 1.0 - nGeo.y;\n float h01 = clamp(hC / max(uHeightScale, 1e-3), 0.0, 1.0);\n float hRel = vWorldPos.y - uSeaLevel;\n\n // banded albedo from the REAL palette uniforms so the interim look already\n // matches the user's style — the boot→full swap reads as added detail, not\n // a colour change\n vec3 albedo = mix(uColGrass, uColDryGrass, smoothstep(0.18, 0.45, h01));\n albedo = mix(albedo, uColRock, smoothstep(0.40, 0.75, h01));\n albedo = mix(albedo, uColRock, clamp(slope * 1.8, 0.0, 1.0) * 0.6);\n albedo = mix(albedo, uColRockHi, smoothstep(0.60, 0.85, h01) * (1.0 - slope));\n albedo = mix(albedo, uColSnow, smoothstep(uSnowLine - 0.08, uSnowLine + 0.06, h01 - slope * 0.25));\n albedo = mix(uColSand, albedo, smoothstep(0.0, 6.0, hRel));\n float luma = dot(albedo, vec3(0.299, 0.587, 0.114));\n albedo = max((mix(vec3(luma), albedo, uPaletteSaturation) - 0.5) * uPaletteContrast + 0.5, vec3(0.0)) * uPaletteTint;\n float graphDetail = fract(sin(dot(floor(xz * 0.02), vec2(12.9898, 78.233))) * 43758.5453);\n albedo = applyTerrainGraphColor(albedo, xz, h01, slope, graphDetail, 0.5);\n\n vec3 n = normalize(vec3(\n nGeo.x * uNormalStrength,\n max(nGeo.y, 0.0001),\n nGeo.z * uNormalStrength\n ));\n float diff = max(dot(n, uSunDir), 0.0);\n vec3 sunCol = uTerrainSunCol * uTerrainSunIntensity;\n vec3 skyAmb = uTerrainSkyAmb * 0.50 * (n.y * 0.5 + 0.5);\n vec3 bounce = uTerrainBounce * 0.25 * (1.0 - n.y * 0.5);\n vec3 col = albedo * (sunCol * diff * (1.0 - terrainCloudShadow(vWorldPos)) + skyAmb + bounce);\n\n float skirtDarken = 0.0;\n col *= 1.0 - skirtDarken;\n\n float fogF = 1.0 - exp(-uFogDensity * uFogDensity * dist * dist);\n col = mix(col, uFogColor, clamp(fogF, 0.0, 1.0));\n gl_FragColor = vec4(pow(col, vec3(1.0 / 2.2)), 1.0);\n}\n`;\n\n// 1x1 mid-grey fallback so the two surface-texture samplers are always bound\n// (avoids \"no texture\" warnings while the real atlas is null / before build).\nlet _surfFallbackTex = null;\nfunction surfFallbackTexture() {\n if (!_surfFallbackTex) {\n _surfFallbackTex = new THREE.DataTexture(new Uint8Array([128, 128, 128, 255]), 1, 1, THREE.RGBAFormat);\n _surfFallbackTex.needsUpdate = true;\n }\n return _surfFallbackTex;\n}\n\nexport function createTerrainUniforms({ surfaceFallback = null } = {}) {\n const paletteUniforms = createPaletteUniforms();\n const defaults = {\n ...DEFAULT_PLANET_STYLE,\n palette: EARTH_PALETTE,\n };\n applyPlanetStyleToUniforms(paletteUniforms, defaults);\n\n return {\n uSeedOffset: { value: new THREE.Vector2(0, 0) },\n uFrequency: { value: 0.002 },\n uHeightScale: { value: 420 },\n uSeaLevel: { value: 42 },\n uTerrainFormationSeaLevel: { value: 42 },\n uAmplitude: { value: 1.0 },\n uStackNormalize: { value: 0.0 },\n uStackOutMin: { value: 0.0 },\n uStackOutMax: { value: 1.35 },\n uTerrainSmoothing: { value: 0.0 },\n uTerrainDetailCut: { value: 0.0 }, // 1 - params.terrainDetail (0 = full detail)\n uPersistence: { value: 0.5 },\n uLacunarity: { value: 2.05 },\n uRidge: { value: 0.65 },\n uWarp: { value: 0.9 },\n uFalloff: { value: 0.5 },\n uEdgeFalloffMode:{ value: 0.0 },\n uInfiniteMode: { value: 0.0 },\n uBoardHalf: { value: 1024 },\n uChunkSize: { value: 128 },\n // Mini Terrain composition (uMiniShape 0 = off: every other mode/project).\n uMiniShape: { value: 0.0 },\n uMiniFrame: { value: new THREE.Vector4(0, 1, 0, 0) },\n uMiniGeometry: { value: new THREE.Vector4(0, 0, 0, 0) },\n uMiniHeights: { value: new THREE.Vector4(0, 0, 0, 0) },\n uMiniWobble: { value: new THREE.Vector4(0, 0, 0, 0) },\n uMiniDetail: { value: new THREE.Vector4(0, 0, 0, 0) },\n // Tile mode (multi-cell studio assembly). Defaults reproduce a single\n // origin-centred board (uUseTiles=0 -> legacy falloff/wall path).\n uTileOccupancy: { value: null },\n uTileGridOrigin: { value: new THREE.Vector2(-1024, -1024) },\n uTileGridDim: { value: new THREE.Vector2(1, 1) },\n uTileCellSize: { value: 2048 },\n uUseTiles: { value: 0.0 },\n uTileShape: { value: 0.0 },\n uTileDiskRadius: { value: 1024 },\n uMoistScale: { value: 1.0 },\n uMoistBias: { value: 0.0 },\n uBiomeScale: { value: 1.0 },\n uTempBias: { value: 0.0 },\n uBiomeDebug: { value: 0.0 },\n uSnowLine: { value: 0.7 },\n uNormalStrength: { value: 1.25 },\n uAO: { value: 0.75 },\n uAORidge: { value: 0.0 },\n uRockSlopeLo: { value: 0.42 },\n uRockSlopeHi: { value: 0.72 },\n uSnowSlopeMin: { value: 0.30 },\n uSnowSlopeMax: { value: 0.62 },\n uGrid: { value: 1.0 },\n uLodDebug: { value: 0.0 },\n uMergeDebug: { value: 0.0 },\n uColorMode: { value: 0.0 },\n uEps: { value: 0.6 },\n // Internal compiler control, not an adjustable quality/sample setting.\n uTerrainNormalSampleCount: { value: 3 },\n uSkirtDepth: { value: 40 },\n uPlinthBaseY: { value: -40 },\n uPlinthColor: { value: new THREE.Color(0x14110d) },\n // Mini Terrain section strata (uMiniCut 0 = legacy flat plinth colour).\n uMiniCut: { value: 0.0 },\n uMiniTerrainBevel: { value: 0.0 },\n uMiniHexagon: { value: 0.0 },\n uMiniBevelRounded: { value: 0.0 },\n uMiniCutTopsoil: { value: new THREE.Vector3(0.05, 0.03, 0.02) },\n uMiniCutBandA: { value: new THREE.Vector3(0.31, 0.23, 0.15) },\n uMiniCutBandB: { value: new THREE.Vector3(0.20, 0.14, 0.09) },\n uMiniCutDeep: { value: new THREE.Vector3(0.07, 0.06, 0.05) },\n uMiniCutSediment:{ value: new THREE.Vector3(0.51, 0.40, 0.21) },\n uMiniCutParams: { value: new THREE.Vector4(8, -70, 12, 330) },\n uAnalysisEnabled: { value: 0.0 },\n uAnalysisMode: { value: 1.0 },\n uAnalysisOpacity: { value: 0.72 },\n uAnalysisMin: { value: 0.0 },\n uAnalysisMax: { value: 600.0 },\n uAnalysisThresholdA: { value: 35.0 },\n uAnalysisThresholdB: { value: 55.0 },\n uAnalysisContourSpacing: { value: 50.0 },\n uAnalysisContourStrength: { value: .35 },\n uWallThickness: { value: 12 },\n uTerrainCloudShadowTex: { value: null },\n uTerrainCloudShadowPlanet: { value: 0.0 },\n uTerrainCloudShadowEnabled: { value: 0.0 },\n uTerrainCloudShadowStrength: { value: 0.45 },\n uTerrainCloudShadowCenter: { value: new THREE.Vector2() },\n uTerrainCloudShadowExtent: { value: 1.0 },\n uTerrainCloudShadowAltitude: { value: 1000.0 },\n // Underwater caustics (shared by every terrain material — studio/infinite\n // declare + use them; the planet material harmlessly ignores them). Default\n // off; the engine enables uCausticBlend while water caustics are active.\n uCausticStrength: { value: 0.0 },\n uCausticBlend: { value: 0.0 },\n uCausticScale: { value: 1.0 },\n uCausticSpeed: { value: 1.0 },\n uCausticColor: { value: new THREE.Vector3(0.85, 0.95, 1.0) },\n uCausticDepthFade:{ value: 70.0 },\n uCausticMinDepth: { value: 1.0 },\n uCausticMinDepthFalloff: { value: 1.0 },\n uCausticWaterAnim: { value: 1.0 },\n uCausticAnimSpeed: { value: 1.0 },\n uCausticWaveSpeed: { value: 1.0 },\n uCausticWaveScale: { value: 1.0 },\n uCausticWaveStrength: { value: 1.6 },\n uCausticLargeWaveStr: { value: 1.0 },\n uCausticSmallWaveStr: { value: 0.65 },\n uCausticRippleLegacy: { value: 1.0 },\n uCausticWaveDir: { value: new THREE.Vector2(1, 0) },\n uPlanetRadius: { value: 8000 },\n uPlanetEps: { value: 0.0015 },\n uSunDir: { value: new THREE.Vector3(0.5, 0.7, 0.3).normalize() },\n uFogColor: { value: new THREE.Color(0x0b0e14) },\n uFogDensity: { value: 0.000045 },\n uTime: { value: 0 },\n uPaintEnabled: { value: 0 },\n uPaintOpacity: { value: 1 },\n uPaintBoardSize: { value: 1024 },\n uPaintResolution:{ value: 512 },\n uPaintBaseMult: { value: 1 },\n uPaintHeightTexture: { value: null },\n uPaintBiomeTexture: { value: null },\n uPaintPropsTexture: { value: null },\n uManualSurfaceMode: { value: 0 },\n uManualBaseGenerated: { value: 0 },\n uManualSurfaceOrigin: { value: new THREE.Vector2(-512, -512) },\n uManualSurfaceSpan: { value: new THREE.Vector2(1024, 1024) },\n uManualSurfaceTextureA: { value: null },\n uManualSurfaceTextureB: { value: null },\n uManualEnabled: { value: 0 },\n uManualOrigin: { value: new THREE.Vector2(-512, -512) },\n uManualSpan: { value: new THREE.Vector2(1024, 1024) },\n uManualHeightTexture: { value: null },\n uSplineEnabled: { value: 0.0 },\n uSplineResolution: { value: 512.0 },\n uSplineOrigin: { value: new THREE.Vector2(-1024, -1024) },\n uSplineSpan: { value: new THREE.Vector2(2048, 2048) },\n uSplineHeightTexture: { value: null },\n uSplineMaskTexture: { value: null },\n uSplineAuxTexture: { value: null },\n // Planet-mode baked height cubemap (shared by the planet terrain +\n // water shaders). When uUsePlanetHeightTex is 1, those shaders sample this\n // texture instead of re-evaluating the ~46-octave height field per pixel.\n // Ignored by the studio/infinite materials, which never declare them.\n uPlanetHeightTex: { value: null },\n uUsePlanetHeightTex: { value: 0.0 },\n uPlanetClimateTex: { value: null }, // PlanetHeightBaker climate cube\n uUsePlanetClimateTex: { value: 0.0 },\n\n // Studio-mode packed height/normal texture (shared by the studio terrain +\n // water shaders). When uUseTerrainHeightTex is 1, those shaders sample this\n // 2D texture instead of re-evaluating the height field per pixel. The shared\n // Tile/Infinite program always declares it; uInfiniteMode keeps unbounded\n // terrain on the procedural path.\n uTerrainHeightTex: { value: null },\n uUseTerrainHeightTex: { value: 0.0 },\n // Procedural climate bake used only by Studio water tinting.\n // RGBA = temperature, moisture, continentalness, region.\n uTerrainBiomeTex: { value: null },\n uUseTerrainBiomeTex: { value: 0.0 },\n // Dedicated Studio water cache. During a rebuild the Engine disables these\n // bindings and hides Studio water; the matching final height + climate\n // textures are then published together.\n uWaterTerrainHeightTex: { value: null },\n // Water depth-test tolerance (WATER_DEPTH_PULL_GLSL), set per render.\n uWaterDepthPull: { value: 0.0 },\n uWaterTerrainBiomeTex: { value: null },\n uUseWaterTerrainBiomeTex: { value: 0.0 },\n uInfiniteFieldTex0: { value: null },\n uInfiniteFieldTex1: { value: null },\n uInfiniteFieldTex2: { value: null },\n uInfiniteFieldOrigin0:{ value: new THREE.Vector2() },\n uInfiniteFieldOrigin1:{ value: new THREE.Vector2() },\n uInfiniteFieldOrigin2:{ value: new THREE.Vector2() },\n uInfiniteFieldSpan0: { value: new THREE.Vector2(1, 1) },\n uInfiniteFieldSpan1: { value: new THREE.Vector2(1, 1) },\n uInfiniteFieldSpan2: { value: new THREE.Vector2(1, 1) },\n uInfiniteFieldReady: { value: new THREE.Vector3() },\n uUseInfiniteFieldCache: { value: 0.0 },\n // Geomorph bands (see terrainMorphedGrid). Defaults disable morphing until\n // the active world publishes its LOD distances.\n uLodMorphStart: { value: new THREE.Vector3(1e9, 1e9, 1e9) },\n uLodMorphEnd: { value: new THREE.Vector3(1e9, 1e9, 1e9) },\n uLodGridSegments: { value: new THREE.Vector4(64, 32, 16, 8) },\n uBakeOrigin: { value: new THREE.Vector2(-1024, -1024) },\n uBakeSpan: { value: new THREE.Vector2(2048, 2048) },\n // Live/baked hybrid (terrainBakeBlendWeight). Texel size is published with\n // the bake; the pixel footprint is refreshed per frame from the camera.\n uBakeTexelWorld: { value: 1.0 },\n uPixelWorldPerDist: { value: 0.0 },\n // Measured (perf-harness bake-threshold sweep): the bilinear bake stays\n // within <0.4% SSIM of the live field down to ~0.3 texels per pixel, and\n // the live stack near the camera was ~60% of the close-view GPU time.\n uBakeBlend: { value: new THREE.Vector2(0.3, 0.5) },\n // Camera-centred fine bake level (NearHeightCache); off until published.\n uNearBakeTex: { value: null },\n uNearBakeOrigin: { value: new THREE.Vector2() },\n uNearBakeSpan: { value: new THREE.Vector2(1, 1) },\n uNearBakeTexelWorld: { value: 0.5 },\n uUseNearBake: { value: 0 },\n\n // Erosion height-offset field (signed world-unit delta over the bake region,\n // R channel). Added in heightAt() so mesh/normals/collision/props/export all\n // follow it. Disabled by default — a free no-op until an erosion bake runs.\n uErosionOffsetTex: { value: null },\n uErosionEnabled: { value: 0.0 },\n uDestructionTexture: { value: null },\n uDestructionEnabled: { value: 0.0 },\n uDestructionOrigin: { value: new THREE.Vector2() },\n uDestructionSpan: { value: new THREE.Vector2(1, 1) },\n\n // Noise Stack per-layer continuous params (shared by every height material).\n // Packed each param change by Engine from the live NoiseStack; the GLSL\n // arrays in COMMON_UNIFORMS_GLSL read these.\n uLayerStrength: { value: new Array(MAX_LAYERS).fill(0) },\n uLayerScale: { value: new Array(MAX_LAYERS).fill(1) },\n uLayerSeed: { value: new Array(MAX_LAYERS).fill(0) },\n uLayerParamsA: { value: Array.from({ length: MAX_LAYERS }, () => new THREE.Vector4()) },\n uLayerParamsB: { value: Array.from({ length: MAX_LAYERS }, () => new THREE.Vector4()) },\n uLayerMaskA: { value: Array.from({ length: MAX_LAYERS }, () => new THREE.Vector4()) },\n uLayerMaskB: { value: Array.from({ length: MAX_LAYERS }, () => new THREE.Vector4()) },\n uLayerMaskC: { value: Array.from({ length: MAX_LAYERS }, () => new THREE.Vector4()) },\n // Terrain graph color stream. Kept separate from height slots so rich\n // color grading never reduces the realtime landform budget.\n uGraphColorA: { value: Array.from({ length: 8 }, () => new THREE.Vector4()) },\n uGraphColorB: { value: Array.from({ length: 8 }, () => new THREE.Vector4()) },\n uGraphColorC: { value: Array.from({ length: 8 }, () => new THREE.Vector4()) },\n uGraphColorD: { value: Array.from({ length: 8 }, () => new THREE.Vector4()) },\n uGraphColorParams: { value: Array.from({ length: 8 }, () => new THREE.Vector4()) },\n uNoiseDebug: { value: 0.0 },\n uTileDebugView: { value: 0.0 },\n uTerrainDetailQuality: { value: 3.0 },\n uDetailPageArray: { value: null },\n uDetailPageCoords: { value: Array.from({ length: 16 }, () => new THREE.Vector3(1e9, 1e9, 0)) },\n uDetailPageBasis: { value: 0 },\n uDetailGlobalBlend: { value: 0 },\n uTerrainDetailScale: { value: 0.16 },\n uTerrainDetailStrength: { value: 0.72 },\n uTerrainDetailNormalStrength: { value: 0.42 },\n uTerrainDetailNear: { value: 80.0 },\n uTerrainDetailFar: { value: 190.0 },\n uTerrainRockSlope: { value: 0.28 },\n uTerrainRockSharpness: { value: 0.14 },\n uTerrainTriplanar: { value: 1.0 },\n uTerrainShoreRange: { value: 18.0 },\n uTerrainShoreWetness: { value: 0.35 },\n uTerrainDetailOpacity: { value: 1.0 },\n uTerrainMicroDetail: { value: 0.6 },\n uTerrainMacroVariation: { value: 0.5 },\n uTerrainDetailDebug: { value: 0.0 },\n uVisualTerrainColorVariation: { value: 0.36 },\n uVisualTerrainHeightDetail: { value: 0.42 },\n uVisualWetShoreStrength: { value: 0.55 },\n uVisualRockDetail: { value: 0.45 },\n uVisualSoilDetail: { value: 0.35 },\n uVisualSandDetail: { value: 0.38 },\n uVisualFoamBreakup: { value: 0.45 },\n uVisualWetSandRange: { value: 18.0 },\n uVisualShallowWaterSoftness: { value: 0.38 },\n uImportNoiseTex: { value: null },\n uImportHeightTex:{ value: null },\n uImportBiomeTex: { value: null },\n uImportImageryTex: { value: null },\n uImportNoiseMode:{ value: 0.0 },\n uImportHeightMode:{ value: 0.0 },\n uImportBiomeMode:{ value: 0.0 },\n uImportImageryMode:{ value: 0.0 },\n uImportNoiseBlend:{ value: 1.0 },\n uImportHeightBlend:{ value: 1.0 },\n uImportHeightStrength:{ value: 1.0 },\n uImportHeightOffset:{ value: 0.0 },\n uImportBiomeBlend:{ value: 1.0 },\n uImportImageryBlend:{ value: 1.0 },\n // World rect the imported height map covers (originX, originZ, spanX, spanZ).\n // Kept in sync with the single origin cell by _syncImportedMapUniforms unless\n // a real-world import widens it to the tile-assembly union.\n uImportHeightRegion:{ value: new THREE.Vector4(-1024, -1024, 2048, 2048) },\n\n // Surface textures (real material maps replacing / tinting the biome colour).\n // Atlas samplers stay null until the engine builds them; uSurfMode 0 keeps the\n // whole feature a no-op (procedural colours), so the shader is unchanged until\n // the user switches to texture mode.\n uSurfaceArrayMode: { value: 0 },\n uSurfaceGraphCode: { value: \"\" },\n uSurfacePaintArray: { value: new THREE.DataArrayTexture(new Uint8Array(4),1,1,1) },\n uSurfacePaintEnabled: { value: 0 },\n uSurfaceContributions: { value: 4 },\n uSurfacePaintRegion: { value: new THREE.Vector4(0,0,1,1) },\n uSurfacePaintOpacity: { value: new Array(32).fill(0) },\n uSurfacePaintMap: { value: Array.from({length:32},()=>new THREE.Vector4(-1,-1,0,1)) },\n uSurfacePaintTint: { value: Array.from({length:32},()=>new THREE.Vector3(1,1,1)) },\n uSurfaceGraphParams: { value: Array.from({length:128},()=>new THREE.Vector4()) },\n uSurfaceRoleMap: { value: Array.from({length:13},()=>new THREE.Vector4(0,-1,0,1)) },\n uSurfaceRoleTint: { value: Array.from({length:13},()=>new THREE.Vector3(1,1,1)) },\n uSurfaceAssetSize: { value: new Array(64).fill(2) },\n uSurfDiffuse: { value: surfaceFallback ?? surfFallbackTexture() },\n uSurfProps: { value: surfaceFallback ?? surfFallbackTexture() },\n uSurfMode: { value: 0.0 },\n uSurfAmount: { value: 1.0 },\n uSurfReveal: { value: 0.0 },\n uSurfTint: { value: 0.0 },\n uSurfPaletteInfluence: { value: 0.6 },\n uSurfScale: { value: 1.0 },\n uSurfBreakup: { value: 0.0 },\n uSurfBlend: { value: 0.0 },\n uSurfTransition: { value: 0.5 },\n uSurfNormalAmt: { value: 1.0 },\n uSurfRoughAmt: { value: 1.0 },\n uSurfAOAmt: { value: 1.0 },\n uSurfTriplanar: { value: 1.0 },\n // Textures render at full strength across the board and near field, easing\n // to procedural colour only in the far distance (cuts cost + no-mip shimmer\n // for far infinite-world terrain). Studio board distances stay well inside.\n uSurfNear: { value: 200.0 },\n uSurfFar: { value: 12000.0 },\n uSurfTile: { value: new Array(SURFACE_TEXTURE_ROLE_COUNT).fill(12) },\n uSurfRolePresent:{ value: new Array(SURFACE_TEXTURE_ROLE_COUNT).fill(0) },\n uSurfPresent: { value: new Array(SURFACE_TEXTURE_ROWS).fill(0) },\n ...paletteUniforms,\n };\n}\n\n// Default stack GLSL (single legacy layer) — used when no stack is supplied so\n// existing call sites stay valid and render exactly as before.\nconst DEFAULT_STACK_GLSL = generateStackGLSL(defaultLegacyStack());\n\nexport function createTerrainMaterial(\n uniforms,\n octaves = 7,\n stackGLSL = DEFAULT_STACK_GLSL,\n options = {},\n) {\n const variant = resolveTerrainVariant(options.variant).name;\n const worldMode = resolveTerrainWorldMode(options.worldMode);\n const h = resolveTerrainVariant(variant).manual\n ? MANUAL_HEIGHT_GLSL\n : buildHeightGLSL(stackGLSL.body2d);\n const material = new THREE.ShaderMaterial({\n uniforms,\n defines: {\n OCTAVES: octaves,\n ...(terrainShaderFamily(variant) === 'surface' ? { STANDARD_PBR_DETAIL: 1 } : {}),\n },\n vertexShader: buildVertex(h, variant, worldMode),\n fragmentShader: buildFragment(\n h,\n stackGLSL.colorBody || DEFAULT_TERRAIN_GRAPH_COLOR_GLSL,\n variant,\n worldMode,\n ),\n side: THREE.DoubleSide,\n extensions: { derivatives: true },\n });\n material.userData.terrainVariant = variant;\n material.userData.terrainWorldMode = worldMode;\n material.userData.heightProgramSig = stackGLSL.sig;\n return installSurfaceMaterialBackend(material);\n}\n\nexport function createInfiniteTerrainMaterial(\n uniforms,\n octaves = 7,\n stackGLSL = DEFAULT_STACK_GLSL,\n options = {},\n) {\n // A distinct material object keeps mode ownership/disposal simple. Infinite\n // uses a cache-only source, while Studio uses the Tile analytic/bake source;\n // keeping those programs separate is necessary to avoid reserving both\n // worlds' sampler sets in a custom-surface fragment shader.\n return createTerrainMaterial(uniforms, octaves, stackGLSL, {\n ...options,\n worldMode: options.worldMode ?? 'infinite',\n });\n}\n\n/**\n * Terrain material with the MINIMAL fragment (see buildMinimalFragment). Same\n * heavy vertex shader — geometry must match exactly — but a fragment that is a\n * fraction of the full source, so ANGLE's synchronous translation is fast.\n * `userData.minimalFragment` marks it for the in-place source upgrade\n * (rebuildTerrainShaderSource) once the full program is warmed.\n */\nexport function createBootTerrainMaterial(uniforms, octaves = 7, stackGLSL = DEFAULT_STACK_GLSL, _options = {}) {\n const h = buildHeightGLSL(stackGLSL.body2d);\n const mat = new THREE.ShaderMaterial({\n uniforms,\n defines: { OCTAVES: octaves },\n vertexShader: buildVertex(h, 'preview'),\n fragmentShader: buildMinimalFragment(stackGLSL.colorBody || DEFAULT_TERRAIN_GRAPH_COLOR_GLSL),\n side: THREE.DoubleSide,\n });\n mat.userData.minimalFragment = true;\n mat.userData.heightProgramSig = stackGLSL.sig;\n return mat;\n}\n\n// Update a live terrain material's shader source to a new generated stack\n// in place (same material object → every mesh referencing it updates). The\n// program for the identical source was warm-compiled first, so the relink is\n// served from three's cache.\nexport function rebuildTerrainShaderSource(mat, stackGLSL, options = {}) {\n const variant = resolveTerrainVariant(options.variant).name;\n const worldMode = resolveTerrainWorldMode(options.worldMode ?? mat?.userData?.terrainWorldMode);\n const h = resolveTerrainVariant(variant).manual\n ? MANUAL_HEIGHT_GLSL\n : buildHeightGLSL(stackGLSL.body2d);\n mat.vertexShader = buildVertex(h, variant, worldMode);\n mat.fragmentShader = buildFragment(\n h,\n stackGLSL.colorBody || DEFAULT_TERRAIN_GRAPH_COLOR_GLSL,\n variant,\n worldMode,\n );\n mat.userData.minimalFragment = false; // boot materials upgrade to the full fragment here\n mat.userData.terrainVariant = variant;\n mat.userData.terrainWorldMode = worldMode;\n mat.userData.heightProgramSig = stackGLSL.sig;\n mat.defines ||= {};\n if (terrainShaderFamily(variant) === 'surface') mat.defines.STANDARD_PBR_DETAIL = 1;\n else delete mat.defines.STANDARD_PBR_DETAIL;\n mat.extensions ||= {};\n mat.extensions.derivatives = true;\n mat.needsUpdate = true;\n}\n\n// Node authoring keeps the inexpensive palette-based fragment while swapping\n// only the generated height source. This preserves a correct live 3D preview\n// without paying for the full surface/detail fragment after every graph edit.\nexport function rebuildTerrainPreviewShaderSource(mat, stackGLSL) {\n const h = buildHeightGLSL(stackGLSL.body2d);\n mat.vertexShader = buildVertex(h, 'preview');\n mat.fragmentShader = buildMinimalFragment(stackGLSL.colorBody || DEFAULT_TERRAIN_GRAPH_COLOR_GLSL);\n mat.userData.minimalFragment = true;\n mat.userData.heightProgramSig = stackGLSL.sig;\n mat.needsUpdate = true;\n}\n","import { MATERIAL_DEFAULTS, MATERIAL_FIELDS, STYLE_DEFAULTS } from './TerrainAppearance.js';\nimport { normalizeTerrainSource } from './TerrainSources.js';\nimport { DEFAULT_PARAMS } from '../engine/presets.js';\nimport { EARTH_PALETTE, PALETTE_KEYS } from '../engine/style/ColorPalette.js';\n\nconst p = DEFAULT_PARAMS;\nconst defaults = {\n heightScale: p.heightScale,\n seaLevel: p.seaLevel,\n terrainFormationSeaLevel: p.terrainFormationSeaLevel,\n material: MATERIAL_DEFAULTS,\n style: STYLE_DEFAULTS,\n layout: { chunkCount: 8, chunkSize: p.chunkSize },\n source: {\n kind: 'noise',\n seed: p.seed,\n octaves: p.octaves,\n scale: p.noiseScale,\n strength: p.noiseStrength,\n smoothing: p.terrainSmoothing,\n detail: p.terrainDetail,\n persistence: p.persistence,\n lacunarity: p.lacunarity,\n ridge: p.ridge,\n warp: p.warp,\n falloff: p.falloff,\n edgeFalloffMode: p.edgeFalloffMode,\n },\n quality: {\n lodSegments: [64, 32, 16, 8],\n lodDistanceScale: 1,\n morphEnabled: true,\n mergeEnabled: true,\n cullingEnabled: true,\n },\n lighting: { sunAzimuth: p.sunAzimuth, sunElevation: p.sunElevation, fogDensity: 0 },\n palette: EARTH_PALETTE,\n};\n\nexport function freezeConfiguration(value) {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.values(value).forEach(freezeConfiguration);\n Object.freeze(value);\n }\n return value;\n}\n\nfunction record(value, allowed, path) {\n if (!value || typeof value !== 'object' || Array.isArray(value))\n throw new TypeError(`${path} must be an object`);\n for (const key of Object.keys(value)) {\n if (!allowed.includes(key)) throw new TypeError(`Unsupported terrain option: ${path}.${key}`);\n }\n}\n\nfunction number(value, min, max, name, integer = false) {\n if (\n !Number.isFinite(value) ||\n value < min ||\n value > max ||\n (integer && !Number.isInteger(value))\n ) {\n throw new RangeError(\n `${name} must be ${integer ? 'an integer' : 'a number'} in [${min}, ${max}]`,\n );\n }\n}\n\nexport function normalizeConfiguration(patch = {}, base = defaults, world = {}) {\n const layoutDefaults = { ...defaults.layout, ...world.layoutDefaults };\n record(patch, Object.keys(defaults), 'configuration');\n const next = { ...base, ...patch };\n for (const key of ['layout', 'quality', 'lighting', 'palette', 'material', 'style']) {\n if (patch[key] !== undefined)\n record(patch[key], Object.keys(key === 'layout' ? layoutDefaults : defaults[key]), key);\n next[key] = { ...(key === 'layout' ? layoutDefaults : {}), ...base[key], ...patch[key] };\n }\n next.quality.lodSegments = [...next.quality.lodSegments];\n next.palette = Object.fromEntries(PALETTE_KEYS.map((key) => [key, [...next.palette[key]]]));\n if (patch.source !== undefined)\n record(\n patch.source,\n [\n ...Object.keys(defaults.source),\n 'stack',\n 'graph',\n 'document',\n 'resolution',\n 'data',\n 'width',\n 'height',\n ],\n 'source',\n );\n next.source =\n patch.source === undefined && Object.isFrozen(base.source)\n ? base.source\n : normalizeTerrainSource(patch.source, base.source, defaults.source, world.mode);\n world.validateLayout?.(next.layout);\n for (const [key, value] of Object.entries(next.material)) {\n const [, fallback, min, max] = MATERIAL_FIELDS[key];\n if (typeof fallback === 'boolean') {\n if (typeof value !== 'boolean') throw new TypeError('material.' + key + ' must be boolean');\n } else number(value, min, max, 'material.' + key, key === 'terrainDetailQuality');\n }\n for (const [key, value] of Object.entries(next.style)) {\n if (Array.isArray(STYLE_DEFAULTS[key])) {\n if (!Array.isArray(value) || value.length !== 3) throw new TypeError('style.' + key + ' requires three components');\n next.style[key] = [...value];\n value.forEach(v => number(v, 0, 10, 'style.' + key));\n } else number(value, 0, 10, 'style.' + key);\n }\n number(next.terrainFormationSeaLevel, -100000, 100000, 'terrainFormationSeaLevel');\n number(next.heightScale, 1, 100000, 'heightScale');\n number(next.seaLevel, -100000, 100000, 'seaLevel');\n number(next.layout.chunkCount, 1, 32, 'layout.chunkCount', true);\n number(next.layout.chunkSize, 1, 100000, 'layout.chunkSize');\n number(next.source.seed, -2147483648, 4294967295, 'source.seed', true);\n number(next.source.octaves, 1, 12, 'source.octaves', true);\n for (const [key, min, max] of [\n ['scale', 0.01, 1000],\n ['strength', 0, 4],\n ['smoothing', 0, 1],\n ['detail', 0, 1],\n ['persistence', 0.01, 1],\n ['lacunarity', 1, 4],\n ['ridge', 0, 2],\n ['warp', 0, 4],\n ['falloff', 0, 1],\n ])\n number(next.source[key], min, max, `source.${key}`);\n if (!['island', 'mountains'].includes(next.source.edgeFalloffMode))\n throw new TypeError('Invalid edgeFalloffMode');\n const segments = next.quality.lodSegments;\n if (segments.length !== 4) throw new TypeError('quality.lodSegments requires four resolutions');\n segments.forEach((v, i) => {\n number(v, 2, 256, 'quality.lodSegments', true);\n if (v % 2 || (i > 0 && segments[i - 1] !== v * 2)) {\n throw new RangeError('LOD resolutions must be even and halve at each level');\n }\n });\n number(next.quality.lodDistanceScale, 0.1, 10, 'quality.lodDistanceScale');\n for (const key of ['morphEnabled', 'mergeEnabled', 'cullingEnabled']) {\n if (typeof next.quality[key] !== 'boolean')\n throw new TypeError(`quality.${key} must be boolean`);\n }\n number(next.lighting.sunAzimuth, -360, 360, 'lighting.sunAzimuth');\n number(next.lighting.sunElevation, -90, 90, 'lighting.sunElevation');\n number(next.lighting.fogDensity, 0, 10, 'lighting.fogDensity');\n for (const key of PALETTE_KEYS) {\n const color = next.palette[key];\n if (color.length !== 3) throw new TypeError(`palette.${key} must be a linear RGB triple`);\n color.forEach((v) => number(v, 0, 1, `palette.${key}`));\n }\n return freezeConfiguration(next);\n}\n\nexport function configurationParameters(c) {\n return {\n ...DEFAULT_PARAMS,\n ...c.layout,\n heightScale: c.heightScale,\n seaLevel: c.seaLevel,\n // This is a generation input; moving the live shoreline does not change it.\n terrainFormationSeaLevel: c.terrainFormationSeaLevel,\n ...c.material,\n seed: c.source.seed,\n octaves: c.source.octaves,\n noiseScale: c.source.scale,\n noiseStrength: c.source.strength,\n terrainSmoothing: c.source.smoothing,\n terrainDetail: c.source.detail,\n persistence: c.source.persistence,\n lacunarity: c.source.lacunarity,\n ridge: c.source.ridge,\n warp: c.source.warp,\n falloff: c.source.falloff,\n edgeFalloffMode: c.source.edgeFalloffMode,\n ...c.lighting,\n };\n}\n","// Shared by the studio and the npm runtime. No DOM, storage or render-loop ownership.\nfunction mulberry32(a) {\n return function () {\n a |= 0; a = (a + 0x6D2B79F5) | 0;\n let t = Math.imul(a ^ (a >>> 15), 1 | a);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport function applyTerrainShapeUniforms(u, p, size) {\n const rng = mulberry32(p.seed >>> 0);\n u.uSeedOffset.value.set(rng() * 2048 - 1024, rng() * 2048 - 1024);\n\n u.uFrequency.value = (p.noiseScale * 0.1) / size;\n u.uHeightScale.value = p.heightScale;\n u.uSeaLevel.value = p.seaLevel;\n u.uTerrainFormationSeaLevel.value = p.terrainFormationSeaLevel;\n u.uAmplitude.value = p.noiseStrength;\n u.uTerrainSmoothing.value = p.terrainSmoothing ?? 0;\n if (u.uTerrainDetailCut) u.uTerrainDetailCut.value = 1 - Math.min(1, Math.max(0, p.terrainDetail ?? 1));\n u.uPersistence.value = p.persistence;\n u.uLacunarity.value = p.lacunarity;\n u.uRidge.value = p.ridge;\n u.uWarp.value = p.warp;\n u.uFalloff.value = p.falloff;\n u.uEdgeFalloffMode.value = p.edgeFalloffMode === 'mountains' ? 1 : 0;\n u.uBoardHalf.value = size / 2;\n u.uChunkSize.value = p.chunkSize;\n}\n\nexport function applyTerrainShadingUniforms(u, p, { size, skirtDepth, wallThickness }) {\n u.uMoistScale.value = p.moistScale;\n u.uMoistBias.value = p.moistBias;\n u.uBiomeScale.value = p.biomeScale;\n u.uTempBias.value = p.tempBias;\n u.uBiomeDebug.value = p.biomeDebug ? 1 : 0;\n u.uSnowLine.value = p.snowLine;\n u.uNormalStrength.value = p.normalStrength;\n u.uAO.value = p.aoStrength;\n u.uAORidge.value = p.aoRidge ?? 0;\n // Slope gates — keep each pair ordered so the smoothstep edges stay valid\n // whatever the two sliders are set to.\n {\n const rockLo = p.rockSlopeLo ?? 0.42;\n const snowMin = p.snowSlopeMin ?? 0.30;\n u.uRockSlopeLo.value = rockLo;\n u.uRockSlopeHi.value = Math.max(p.rockSlopeHi ?? 0.72, rockLo + 0.01);\n u.uSnowSlopeMin.value = snowMin;\n u.uSnowSlopeMax.value = Math.max(p.snowSlopeMax ?? 0.62, snowMin + 0.01);\n }\n u.uGrid.value = p.chunkGrid ? 1 : 0;\n u.uLodDebug.value = p.lodDebug ? 1 : 0;\n u.uEps.value = Math.max(0.35, size / 4096);\n u.uSkirtDepth.value = skirtDepth;\n u.uPlinthBaseY.value = -skirtDepth; // perimeter wall drops to plinth base\n u.uWallThickness.value = wallThickness;\n}\n"],"names":["defaults","p"],"mappings":";;;;;;AAmBY,MAAC;AAAA;AAAA,EAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyI/B,MAAC;AAAA;AAAA,EAA8B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqRD,MAAC;AAAA;AAAA,EAAsC;AAAA,EACjD,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACDvC,MAAM,eAAe;AAAA,EAC1B;AAAA,EAAQ;AAAA,EAAW;AAAA,EAAQ;AAAA,EAAQ;AAAA,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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { v as validateParameters, e as effectQuality, r as resolveEffectHost, R as RevisionScope, W as WATER_QUALITY_DEFAULTS } from "./RevisionScope-
|
|
1
|
+
import { v as validateParameters, e as effectQuality, r as resolveEffectHost, R as RevisionScope, W as WATER_QUALITY_DEFAULTS } from "./RevisionScope-Dl4yjj-0.js";
|
|
2
2
|
import * as THREE from "three";
|
|
3
|
-
import { b as buildWaterHeightShaderParts, W as WATER_TERRAIN_CACHE_GLSL, c as createInfiniteWaterMaterial, a as createWaterMaterial, r as rebuildWaterShaderSource } from "./WaterMaterial-
|
|
4
|
-
import { f as fitNearBake, C as COMMON_UNIFORMS_GLSL, d as WATER_TILE_MASK_GLSL, W as WATER_DEPTH_PULL_GLSL } from "./biomeGLSL-
|
|
3
|
+
import { b as buildWaterHeightShaderParts, W as WATER_TERRAIN_CACHE_GLSL, c as createInfiniteWaterMaterial, a as createWaterMaterial, r as rebuildWaterShaderSource } from "./WaterMaterial-C9h1xm9E.js";
|
|
4
|
+
import { f as fitNearBake, C as COMMON_UNIFORMS_GLSL, d as WATER_TILE_MASK_GLSL, W as WATER_DEPTH_PULL_GLSL } from "./biomeGLSL-DWGDDBqT.js";
|
|
5
5
|
import { P as PALETTE_UNIFORMS_GLSL } from "./TerrainSources-Bye63rDk.js";
|
|
6
6
|
import { g as generateStackGLSL, d as defaultLegacyStack } from "./noiseStackCodegen-C4R9Y3L8.js";
|
|
7
7
|
import { W as WATER_OPTICS_GLSL, g as WATER_GEOMETRY_WAVES_GLSL, h as WATER_WAVES_GLSL, f as createMiniWaterSurfaceMaterial, e as applyMiniWaterLook, d as miniWaterColors, r as resolveMiniReflections } from "./MiniWaterMaterial-DLml0hi6.js";
|
|
8
8
|
import { W as WATER_LIGHTING_UNIFORMS_GLSL, c as createWaterLightingUniforms, a as applyWaterLightingUniforms } from "./waterLightingGLSL-DJ1lX0Cy.js";
|
|
9
9
|
import { W as WATER_DEFAULT_PARAMS, i as isRealisticWaterMode, h as isWaterActive, j as resolveEffectiveWaterMode, m as migrateWaterParams, a as WATER_MODES, k as resolveUnderwaterMode } from "./ProgramHealthGate-DS47bUo1.js";
|
|
10
|
-
import { a as createPlanetWaterMaterial } from "./PlanetMaterial-
|
|
10
|
+
import { a as createPlanetWaterMaterial } from "./PlanetMaterial-Cs-_x6vr.js";
|
|
11
11
|
import { a as canvasPngBytes, c as createExportCanvas } from "./ExportEncoding-DOxfYfK8.js";
|
|
12
12
|
import { c as compileMaterial } from "./CompileMaterial-DZiWxNZC.js";
|
|
13
13
|
const PROCEDURAL_SKY_UNIFORMS_GLSL = (
|
|
@@ -177,12 +177,15 @@ const buildFragmentSource = (stackGLSL, infinite = false) => {
|
|
|
177
177
|
vec4 waterBiomeClimateAt(vec2 xz) {
|
|
178
178
|
float available = 0.0;
|
|
179
179
|
vec4 field = infiniteFieldSampleAt(xz, available);
|
|
180
|
+
vec4 result = vec4(0.0);
|
|
180
181
|
if (available > 0.5) {
|
|
181
182
|
float region = fbm3((xz * uFrequency + uSeedOffset) * 0.700 + vec2(631.4, 199.2));
|
|
182
|
-
|
|
183
|
+
result = vec4(field.g, field.b, field.a, region);
|
|
184
|
+
} else {
|
|
185
|
+
Climate climate = climateAt(xz * uFrequency + uSeedOffset);
|
|
186
|
+
result = vec4(climate.temp, climate.moist, climate.cont, climate.region);
|
|
183
187
|
}
|
|
184
|
-
|
|
185
|
-
return vec4(climate.temp, climate.moist, climate.cont, climate.region);
|
|
188
|
+
return result;
|
|
186
189
|
}
|
|
187
190
|
|
|
188
191
|
float waterBiomeClimateAvailable() {
|
|
@@ -193,14 +196,17 @@ float waterBiomeClimateAvailable() {
|
|
|
193
196
|
/* glsl */
|
|
194
197
|
`
|
|
195
198
|
vec4 waterBiomeClimateAt(vec2 xz) {
|
|
199
|
+
vec4 result = vec4(0.0);
|
|
196
200
|
if (uUseWaterTerrainBiomeTex > 0.5) {
|
|
197
201
|
// the bake holds temp, moist, cont, erosion; region jitter is evaluated live
|
|
198
202
|
vec4 baked = textureLod(uWaterTerrainBiomeTex, waterBakedUvAt(xz), 0.0);
|
|
199
203
|
float region = fbm3((xz * uFrequency + uSeedOffset) * 0.700 + vec2(631.4, 199.2));
|
|
200
|
-
|
|
204
|
+
result = vec4(baked.r, baked.g, baked.b, region);
|
|
205
|
+
} else {
|
|
206
|
+
Climate climate = climateAt(xz * uFrequency + uSeedOffset);
|
|
207
|
+
result = vec4(climate.temp, climate.moist, climate.cont, climate.region);
|
|
201
208
|
}
|
|
202
|
-
|
|
203
|
-
return vec4(climate.temp, climate.moist, climate.cont, climate.region);
|
|
209
|
+
return result;
|
|
204
210
|
}
|
|
205
211
|
|
|
206
212
|
float waterBiomeClimateAvailable() {
|
|
@@ -4186,4 +4192,4 @@ export {
|
|
|
4186
4192
|
TerrainWater as T,
|
|
4187
4193
|
createWater as c
|
|
4188
4194
|
};
|
|
4189
|
-
//# sourceMappingURL=TerrainWater-
|
|
4195
|
+
//# sourceMappingURL=TerrainWater-CX4XdwA0.js.map
|