From d26898a3e89145bb0bef2dd393422ccc7f9222a0 Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Sat, 26 Sep 2026 06:00:26 +0100
Subject: [PATCH] MONKEY (visualfix): whitecaps break in scattered short runs,
not a lattice
The fold mask came from the two long wave bands only, which cross in a regular diamond lattice:
every crest broke into the same elongated patch. The mask adds the next two bands' compression,
a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd warped
segments break each crest into short runs. Geometry is unchanged; crest foam stays on steep waves.
Co-Authored-By: Claude Opus 5.5
---
WATER.md | 2 +-
.../src/shaders/enhanced_water.wgsl | 32 +++++++++++++++++--
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/WATER.md b/WATER.md
index ce61136c..fdf94e02 100644
--- a/WATER.md
+++ b/WATER.md
@@ -43,7 +43,7 @@ fork. Do not remove any of them.
|---|---|---|
| Quality tiers | `Classic` = the reference water, `Enhanced`, `High` (= Enhanced + scenery reflections) | Video options → Water Quality, cvar `waterQuality`, env `WOW_WATER=0\|1\|2` |
| Procedural waves | multi-band analytic waves with exact normals; the long swell moves ocean vertices | tier |
-| Open-sea whitecaps | High adds Gerstner crest gathering and fold-thresholded foam; calm canals remain clear | tier |
+| Open-sea whitecaps | High adds Gerstner crest gathering and fold-thresholded foam; calm canals remain clear. The fold mask adds the next two wave bands, a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd segments break each crest into short runs, so the caps do not form a lattice (`whitecap_fold`, `enhanced_water.wgsl`) | tier |
| Near mesh refinement | High builds a transient 4x liquid lattice within 64 yd, with an 80 yd release ring | tier |
| Ocean / inland profiles | the sea and lakes/rivers have their own colour, energy and reflectivity | tier |
| WMO pools | exterior canals use a calm drifting profile with outdoor glints; interiors use their authored MOMT colour and room-fog reflection | tier |
diff --git a/crates/benilla-assets/src/shaders/enhanced_water.wgsl b/crates/benilla-assets/src/shaders/enhanced_water.wgsl
index a8906e63..5a83f818 100644
--- a/crates/benilla-assets/src/shaders/enhanced_water.wgsl
+++ b/crates/benilla-assets/src/shaders/enhanced_water.wgsl
@@ -282,6 +282,24 @@ fn water_gerstner(p: vec2, t: f32, shore: f32) -> vec4 {
return vec4(displacement, fold);
}
+// MONKEY (visualfix): the whitecap fold. The two long bands alone cross in a regular diamond
+// lattice, so every crest broke at the same spots; the next two bands (non-integer length and
+// direction ratios to the first two) add their compression at a reduced weight. Mask only: the
+// displaced geometry still moves on the two long bands.
+fn whitecap_fold(p: vec2, t: f32, shore: f32) -> f32 {
+ var fold = water_gerstner(p, t, shore).w;
+ let energy = clamp(water.mode.y, 0.0, 1.0);
+ let tempo = mix(0.4, 1.0, sqrt(energy));
+ for (var i = 2u; i < 4u; i += 1u) {
+ let wave = WATER_WAVES[i];
+ let direction = vec2(cos(wave.x), sin(wave.x));
+ let k = 6.2831853 / wave.y;
+ let phase = k * (dot(direction, p) - sqrt(10.72 / k) * tempo * t) + wave.w;
+ fold += 0.55 * WATER_GERSTNER_CHOP * wave.z * energy * shore * k * sin(phase);
+ }
+ return max(fold, 0.0);
+}
+
// MONKEY (water): seam for the later wet-weather lane. Its result is a height-gradient
// perturbation in world XZ; zero preserves today's image until that lane supplies rain data.
// MONKEY (wet): rain rings. Each layer is a grid of cells, one drop per cell at a hashed spot and
@@ -634,7 +652,7 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec
let wave = water_waves(p, t, length(eye_pos), footprint, shore, false);
var open_sea_fold = 0.0;
if water.mode.x > 1.5 && ocean_mesh {
- open_sea_fold = water_gerstner(p, t, shore).w;
+ open_sea_fold = whitecap_fold(p, t, shore);
}
// One surface gradient: the procedural bands and the shore break.
let surf_grad = wave.yz + shore_grad + rain_ripple_normal(in.world_position.xyz, footprint,
@@ -840,7 +858,17 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec
// MONKEY (water): WarcraftXL's white belongs to a connected horizontal fold, not to every
// steep normal. Threshold jitter stops identical crests drawing identical white contours.
let fold_jitter = (noise - 0.5) * 0.04;
- let breaking = smoothstep(0.13, 0.25, open_sea_fold + fold_jitter);
+ // MONKEY (visualfix): a slow, domain-warped gust field (tens of yards, unrelated to the wave
+ // lengths) moves the break threshold and calms whole patches, so the caps do not repeat.
+ let gust_warp = vec2(foam_noise(p * 0.013 + vec2(t * 0.004, 7.3)),
+ foam_noise(p * 0.013 + vec2(-4.1, t * 0.003))) - vec2(0.5);
+ let gust = foam_noise(p * 0.037 + gust_warp * 2.3 + t * vec2(0.006, -0.005));
+ let cap_lo = 0.13 + (0.5 - gust) * 0.16;
+ // A crest breaks in short runs, not along its whole length: ~6 yd warped segments.
+ let segment = smoothstep(0.42, 0.62,
+ foam_noise(p * 0.16 + gust_warp * 1.7 + t * vec2(0.011, 0.007)));
+ let breaking = smoothstep(cap_lo, cap_lo + 0.14, open_sea_fold + fold_jitter)
+ * smoothstep(0.25, 0.6, gust) * segment;
let whitecap_alpha = FOAM_WHITECAP * breaking * mix(0.55, 1.0, breakup)
* smoothstep(0.55, 1.0, energy);
// The cached derivatives from the top of the function — same expression as before, one tap.