From 02999791211e250867c845f7dcf4f528916fed7f Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Sat, 26 Sep 2026 17:53:32 +0100
Subject: [PATCH] MONKEY (fix-wind): visible foliage wind + Foliage Wind
Strength slider
The owner's "wind does not work" was the motion being too small to see (WXL's
0.06 yd grass tips on a 6 s wave, 0.1 yd tree crowns). Retuned at the 9 yd/s
base speed:
- grass: tips about +-0.2-0.3 yd, 2.5 s main wave (182 cycles/wrap), 1.1 s
cross flutter (410); 55 yd gust bands roll downwind (68), swelling the sway
to 1.2x and lulling it to 0.55x; a bent tip dips (no stretched blades).
Parting around units unchanged.
- trees/bushes: 4 s crown sway (114), 5.5 s side wave (82), 0.8 s leaf
flutter (569); crown weight grows with height (0.43x at 1.5 yd, 1x at 6 yd,
1.3x cap), so canopies sway about +-0.3 yd while bushes nod and bases stay
planted.
- new cvar foliageWindStrength 0.25..3 (default 1, not on the preset ladder):
Advanced Graphics slider under Foliage Wind (EN+RU label/tooltip), greyed
while Foliage Wind is Off; carried in sway_strength, so the parting ignores
it; a storm adds up to +60 % sway.
Co-Authored-By: Claude Opus 5.5
---
LIGHTING.md | 2 +-
crates/benilla-app/assets/ui/OptionsFrame.xml | 34 ++++++++++-
crates/benilla-app/src/cvars.rs | 7 +++
crates/benilla-app/src/monkey_gfx.rs | 12 +++-
.../benilla-assets/src/shaders/wind_hook.wgsl | 51 ++++++++++------
crates/benilla-world/src/wind/mod.rs | 60 ++++++++++++++++++-
6 files changed, 142 insertions(+), 24 deletions(-)
diff --git a/LIGHTING.md b/LIGHTING.md
index 651e4aa7..03ec8577 100644
--- a/LIGHTING.md
+++ b/LIGHTING.md
@@ -31,7 +31,7 @@ behind each constant.
| Sky quality | Enhanced: the five Light.dbc sky stops through a smooth monotone curve in linear light (no bands at the rings), a soft sun glow tinted by sun and fog colour (fades at night and under cloud), a procedural star field with twinkle and a faint Milky Way over the stock `Stars.m2`. High adds domain-warped cloud detail and sun-lit clouds (self-shadow, silver lining; technique from WarcraftXL, see `THIRD-PARTY.md`). Classic is the reference sky unchanged | Advanced Graphics → Sky Quality, cvar `skyQuality` 0 Classic / 1 Enhanced / 2 High, env `WOW_SKY_QUALITY` |
| Modern fog | MONKEY (fog): radial distance fog with a gradual exponential curve, a daylight sun lobe, and an end colour shared by the world, WDL hull, sky horizon and volumetric haze. Each zone keeps its authored fog end up to the reference view-distance limit; optional `LightFogBand.dbc` rows provide height, sun and end-fog controls. Interior WMO fog stays classic | Advanced Graphics → Modern Fog, cvar `fogModel` 0 Classic (default) / 1 Modern (High = 1), env `WOW_FOGMODEL=0\|1`; `fog_hook.wgsl`, `lighting/fog_model.rs`, `light/fog_band.rs` |
| Rain on surfaces | MONKEY (wet): rain gradually darkens and saturates exposed terrain and models, adds puddles and a sky/sun sheen, and produces rings on exterior Enhanced water. Wetness rises over roughly 90 seconds and dries over roughly four minutes; MONKEY (rainshelter): a camera-centred rain-occlusion map (128² cells of 0.75 yd, filled by vertical collision rays) keeps ground, walls and water under roofs, porches and bridges dry, with a ~1.5 yd drip margin and a soft edge; tree canopies without colliders do not shelter | Advanced Graphics → Wet Surfaces in Rain, cvar `rainSurfaces` 0/1 (default and High 1), env `WOW_RAIN_SURFACES`, `WOW_WETNESS`, `WOW_WET_T`; `weather/wetness.rs`, `weather/shelter.rs`, `wet_hook.wgsl` |
-| Foliage wind | one weather-fed gust/veer field drives grass and classified tree/bush foliage; grass also parts around the player and nearby units | Advanced Graphics → Foliage Wind, cvar `foliageWind` 0 Off / 1 Grass / 2 Grass + Trees (High = 2), capture override `WOW_FOLIAGE_WIND` (dev build + `WOW_CAPTURE` only); field and benders in `benilla-world/src/wind/`. The wave phase is the CPU-integrated travel (speed integrated over time, wrapped seamlessly), and the waves' spatial term uses the fixed profile heading; the veer turns only the bend. Tree exile copies sway through material marker bit 14 (`FOLIAGE_WIND_MARKER`). Capture offset `WOW_CAPTURE_WIND_T` (s). Known: shadow casters (world-shadow proxies, `torch_depth.wgsl`) do not apply the sway, so leaves sample a static shadow; a future world-camera DepthPrepass must call the same offsets |
+| Foliage wind | one weather-fed gust/veer field drives grass and classified tree/bush foliage; grass also parts around the player and nearby units | Advanced Graphics → Foliage Wind, cvar `foliageWind` 0 Off / 1 Grass / 2 Grass + Trees (High = 2), plus the slider Foliage Wind Strength, cvar `foliageWindStrength` 0.25..3 (default 1, not on the preset ladder; a gain on the sway, not the parting; a storm adds up to +60 %). Tuning at 1 (fix-wind): grass tips about ±0.2-0.3 yd on a 2.5 s wave with 1.1 s cross flutter, gust bands 55 yd wide rolling downwind; tree crowns about ±0.3 yd (lever-arm weight grows with height to 1.3x at 8.5 yd) on a 4 s sway, 5.5 s side wave, 0.8 s leaf flutter; bent tips dip slightly. capture override `WOW_FOLIAGE_WIND` (dev build + `WOW_CAPTURE` only); field and benders in `benilla-world/src/wind/`. The wave phase is the CPU-integrated travel (speed integrated over time, wrapped seamlessly), and the waves' spatial term uses the fixed profile heading; the veer turns only the bend. Tree exile copies sway through material marker bit 14 (`FOLIAGE_WIND_MARKER`). Capture offset `WOW_CAPTURE_WIND_T` (s). Known: shadow casters (world-shadow proxies, `torch_depth.wgsl`) do not apply the sway, so leaves sample a static shadow; a future world-camera DepthPrepass must call the same offsets |
| Ambient occlusion | soft contact shadows under props, in wall-floor corners and around feet; depth-only SSAO at half resolution (depth-reconstructed normals, 6/12 spiral taps, 4x4 depth-aware blur, bilateral upsample) multiplied into the opaque scene after the opaque pass and before the water copy, so water, particles and UI are untouched; fades out 45-90 yd (Low) / 60-120 yd (High), spares sky and bright pixels; on alpha-tested foliage (a dense window of two-sided depth spikes) the blur widens its depth tolerance and the AO drops 40 %, which removes the leaf grain while planes, silhouettes and contact creases keep full AO (`benilla-app/src/ssao.rs`, `benilla-app/src/shaders/ssao.wgsl`) | Advanced Graphics → Ambient Occlusion (Off/Low/High; default Off, High preset 2), cvar `ambientOcclusion`, env `WOW_AO=0\|1\|2`, `WOW_AO_DEBUG=1..5` diagnostic views (factor, protection, distance, raw occlusion, foliage mask), `WOW_AO_GAIN`/`_RADIUS`/`_BIAS`/`_STRENGTH` tuning |
| Lamp fog | MONKEY (lampfog): the fog fullscreen pass selects the nearest resolved exterior and interior point-table sources and analytically integrates their softened inverse-square light over the camera-to-scene-depth ray. A near-field haze floor makes lamps visible inside the ordinary volumetric haze's 10 yd clear zone; it is zero by day, stronger in rain/fog, and deliberately unshadowed. The table colour already includes intensity, flame flicker and `fireLightGain`. Halo output uses destination headroom and stays below 1.0, so a later bloom pass cannot blow it out | Advanced Graphics → Lamp Fog, cvar `lampFog` 0 Off (default, unchanged) / 1 Low (16 lamps) / 2 High (32 lamps; Graphics preset High = 2), env `WOW_LAMPFOG=0\|1\|2`; `benilla-app/src/volumetric_fog.rs` |
| City window daylight | MONKEY (fix-daylight/integration): a district WMO's oversized window batch (e.g. the Cathedral of Light's stained-glass walls) is split into window-sized daylight apertures, so the nave takes daylight pane by pane. Applies to WMOs loaded after a change | Advanced Graphics → City Window Daylight, cvar `daylightWindowSplit` 0/1 (default 1, the merged behaviour); `benilla-world/src/lighting/daylight.rs` (`set_window_split`), bridge `benilla-app/src/dynamic_interior.rs` |
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 74dc3699..dc95de76 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -543,6 +543,7 @@
foliageWind = "Foliage Wind",
windGrass = "Grass",
windTrees = "Grass + Trees",
+ foliageWindStrength = "Foliage Wind Strength", -- MONKEY (fix-wind)
-- MONKEY (ao): localised contact-shadow row.
ambientOcclusion = "Ambient Occlusion",
-- MONKEY (lampfog)
@@ -707,6 +708,11 @@
"Grass bends in two travelling wind waves and parts around nearby characters. "
.. "Grass + Trees also gives classified tree and bush foliage a gentle sway.\n\n"
.. "Off makes no vertex displacement and restores the unchanged image. Tree shadows stay static.",
+ -- MONKEY (fix-wind)
+ FOLIAGE_WIND_STRENGTH =
+ "How far grass and foliage swing in the wind. 100% is the standard sway; lower "
+ .. "is calmer, higher is a blustery day. Storms swing everything harder on top. "
+ .. "Grass still parts around characters the same way. Needs Foliage Wind on.",
-- MONKEY (ao): what the contact shadows touch and what they spare.
AMBIENT_OCCLUSION =
"Soft contact shadows where surfaces meet: under props and furniture, in the corners "
@@ -782,6 +788,7 @@
foliageWind = "Ветер листвы",
windGrass = "Трава",
windTrees = "Трава + деревья",
+ foliageWindStrength = "Сила ветра листвы", -- MONKEY (fix-wind)
-- MONKEY (ao): Russian contact-shadow row.
ambientOcclusion = "Затенение окружения",
-- MONKEY (lampfog)
@@ -956,6 +963,11 @@
"Трава гнётся двумя бегущими волнами ветра и расступается вокруг персонажей. "
.. "Трава + деревья также добавляет мягкое качание листвы деревьев и кустов.\n\n"
.. "Выключено не смещает вершины и восстанавливает исходный вид. Тени деревьев остаются неподвижными.",
+ -- MONKEY (fix-wind)
+ FOLIAGE_WIND_STRENGTH =
+ "Насколько сильно трава и листва качаются на ветру. 100% — обычное качание; меньше — "
+ .. "спокойнее, больше — ветреный день. В грозу всё качается ещё сильнее. "
+ .. "Трава по-прежнему расступается вокруг персонажей. Работает, если включён ветер листвы.",
-- MONKEY (ao): Russian contact-shadow explanation.
AMBIENT_OCCLUSION =
"Мягкие контактные тени там, где поверхности соприкасаются: под предметами и мебелью, "
@@ -1049,6 +1061,8 @@
"RowRainSurfaces",
-- MONKEY (wind): appended for conflict-free lane merges.
"RowFoliageWind",
+ -- MONKEY (fix-wind): the sway strength, right under its tier.
+ "RowFoliageWindStrength",
-- MONKEY (ao): appended so the other rows keep their indices.
"RowAmbientOcclusion",
-- MONKEY (lampfog)
@@ -1574,6 +1588,9 @@
-- MONKEY (daylight: terrain torch casters): the ground only casts for outdoor fires.
OptionsRow_SetEnabled(getglobal(base .. "RowTorchTerrainShadows"),
GetCVar("exteriorShadows") ~= "0")
+ -- MONKEY (fix-wind): the strength only matters while some foliage sways.
+ OptionsRow_SetEnabled(getglobal(base .. "RowFoliageWindStrength"),
+ GetCVar("foliageWind") ~= "0")
return
end
if key == "Controls" then
@@ -3260,12 +3277,25 @@
})
-
-
+
+
+
+ OptionsRow_OnLoad(self, "foliageWindStrength",
+ BENILLA_ADVGFX.foliageWindStrength,
+ "BENILLA_TOOLTIP_FOLIAGE_WIND_STRENGTH")
+ OptionsSliderRow_Setup(self, 0.25, 3, 0.25)
+
+
+
+
+
+
+
+
OptionsRow_OnLoad(self, "ambientOcclusion", BENILLA_ADVGFX.ambientOcclusion,
"BENILLA_TOOLTIP_AMBIENT_OCCLUSION")
diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs
index 8e6286cb..ff90cf2e 100644
--- a/crates/benilla-app/src/cvars.rs
+++ b/crates/benilla-app/src/cvars.rs
@@ -964,6 +964,12 @@ pub(crate) const REGISTERED: &[Registered] = &[
"2",
"benilla's own: foliage wind, 0 Off / 1 Grass / 2 Grass + trees",
),
+ // MONKEY (fix-wind): the sway strength slider under Foliage Wind; not on the preset ladder.
+ ours(
+ "foliageWindStrength",
+ "1",
+ "benilla's own: foliage wind strength, a gain on the sway, 0.25..3 (1 = the shipped tuning)",
+ ),
// MONKEY (fog): the distance-fog model. 0 = the 1.12 linear fog (byte-identical), 1 = Modern
// (exponential, sun/horizon colour, end-fog shift, fog end decoupled from farclip past 777).
// Default 0; the Graphics preset's High sets 1.
@@ -4420,6 +4426,7 @@ mod tests {
app.init_resource::();
// MONKEY (wind)
app.init_resource::();
+ app.init_resource::(); // MONKEY (fix-wind)
app.init_resource::(); // MONKEY (wet)
app.add_observer(crate::monkey_gfx::on_cvar);
},
diff --git a/crates/benilla-app/src/monkey_gfx.rs b/crates/benilla-app/src/monkey_gfx.rs
index 8b40b233..261b48eb 100644
--- a/crates/benilla-app/src/monkey_gfx.rs
+++ b/crates/benilla-app/src/monkey_gfx.rs
@@ -6,12 +6,13 @@
//! |---|---|---|---|---|
//! | `skyDither` | 0 Off / 1 On | 0 | 1 | `benilla_world::ffx_glow::SkyDither` |
//! | `foliageWind` | 0 Off / 1 Grass / 2 Grass + trees | 2 | 2 | `benilla_world::wind::FoliageWind` |
+//! | `foliageWindStrength` | 0.25..3 sway gain | 1 | not governed | `benilla_world::wind::FoliageWindStrength` |
//! | `fogModel` | 0 Classic / 1 Modern | 0 | 1 | `benilla_world::lighting::FogModelSetting` |
//! | `rainSurfaces` | 0 Off / 1 On | 1 | 1 | `benilla_world::weather::RainSurfaces` |
use benilla_world::ffx_glow::SkyDither;
use benilla_world::lighting::FogModelSetting;
-use benilla_world::wind::FoliageWind;
+use benilla_world::wind::{FoliageWind, FoliageWindStrength};
use bevy::prelude::*;
use benilla_world::weather::RainSurfaces; // MONKEY (wet)
@@ -20,6 +21,7 @@ pub(crate) fn on_cvar(
ev: On,
mut dither: ResMut,
mut foliage_wind: ResMut,
+ mut wind_strength: ResMut, // MONKEY (fix-wind)
// MONKEY (fog)
mut fog_model: ResMut,
mut rain: ResMut, // MONKEY (wet)
@@ -41,6 +43,13 @@ pub(crate) fn on_cvar(
foliage_wind.0 = want;
}
}
+ // MONKEY (fix-wind): the sway gain, live, clamped to the slider's 0.25..3.
+ "foliagewindstrength" => {
+ let want = FoliageWindStrength::from_cvar(ev.num());
+ if *wind_strength != want {
+ *wind_strength = want;
+ }
+ }
// MONKEY (fog): `WOW_FOGMODEL` still wins for the session (captures).
"fogmodel" => fog_model.set_from_cvar(ev.num()),
// MONKEY (wet): wet surfaces + rain ripples.
@@ -74,6 +83,7 @@ impl Plugin for MonkeyGfxPlugin {
.insert_resource(FoliageWind(
foliage_wind_pin().unwrap_or(FoliageWind::REGISTERED),
))
+ .init_resource::() // MONKEY (fix-wind)
.init_resource::()
.init_resource::() // MONKEY (wet)
.add_observer(on_cvar);
diff --git a/crates/benilla-assets/src/shaders/wind_hook.wgsl b/crates/benilla-assets/src/shaders/wind_hook.wgsl
index 28e60f68..0b61c428 100644
--- a/crates/benilla-assets/src/shaders/wind_hook.wgsl
+++ b/crates/benilla-assets/src/shaders/wind_hook.wgsl
@@ -21,6 +21,14 @@ fn wind_base_dir(frame: MonkeyFrame) -> vec2 {
return vec2(cos(frame.wind_a.z), sin(frame.wind_a.z));
}
+// MONKEY (fix-wind): gusts rolling across a field: a 55 yd band travelling downwind (68 cycles
+// per wrap, about 8 yd/s at the base speed) that swells the sway to 1.2x and lulls it to 0.55x.
+fn wind_gust_roll(frame: MonkeyFrame, xz: vec2) -> f32 {
+ let band = sin(dot(wind_base_dir(frame), xz) * (WIND_TAU / 55.0)
+ - wind_travel_phase(frame.wind_b.x, 68.0));
+ return mix(0.55, 1.2, 0.5 + 0.5 * band);
+}
+
fn wind_cross_dir(dir: vec2) -> vec2 {
// WarcraftXL's secondary wave is 35 degrees off the primary.
return vec2(
@@ -51,19 +59,20 @@ fn grass_offset(
let base_secondary = wind_cross_dir(base_primary);
let wave_primary = WIND_TAU / 18.0;
let wave_secondary = WIND_TAU / 6.5;
- // WarcraftXL's k * 0.33 * speed (primary) and k * 0.33 * 1.37 * speed (secondary), as whole
- // cycles per wrap: 75 and 285.
+ // MONKEY (fix-wind): visible tuning. At the 9 yd/s base speed the primary wave runs a 2.5 s
+ // period (182 cycles per wrap) and the cross flutter 1.1 s (410); both speed up with gusts.
let phase_primary = wave_primary * dot(base_primary, world.xz)
- - wind_travel_phase(frame.wind_b.x, 75.0) + tuft_phase;
+ - wind_travel_phase(frame.wind_b.x, 182.0) + tuft_phase;
let phase_secondary = wave_secondary * dot(base_secondary, world.xz)
- - wind_travel_phase(frame.wind_b.x, 285.0) + tuft_phase * 1.7;
+ - wind_travel_phase(frame.wind_b.x, 410.0) + tuft_phase * 1.7;
let gust = 1.0 + 0.5 * (frame.wind_a.w - 0.5) * 2.0;
let variance = mix(0.7, 1.3, fract(sin(tuft_phase * 12.9898) * 43758.5453));
let distance_fade = 1.0 / (1.0 + distance(camera, world) * 0.015);
var xz = (
- dir_primary * 0.060 * (sin(phase_primary) + 0.35)
- + dir_secondary * 0.020 * sin(phase_secondary)
- ) * gust * variance * weight * distance_fade * frame.wind_b.y * frame.wind_b.z;
+ dir_primary * 0.18 * (sin(phase_primary) + 0.35)
+ + dir_secondary * 0.06 * sin(phase_secondary)
+ ) * gust * variance * wind_gust_roll(frame, world.xz) * weight * distance_fade
+ * frame.wind_b.y * frame.wind_b.z;
let count = min(u32(frame.misc.x), 8u);
for (var i = 0u; i < 8u; i += 1u) {
@@ -77,7 +86,8 @@ fn grass_offset(
xz += radial * 0.50 * edge * cone * weight * frame.wind_b.z;
}
}
- return vec3(xz.x, 0.0, xz.y);
+ // MONKEY (fix-wind): a bent tip dips, so a blade reads as bending rather than stretching.
+ return vec3(xz.x, -min(0.6 * dot(xz, xz), 0.25), xz.y);
}
// MONKEY (wind): our tree/bush design (WarcraftXL has no tree wind). Only leaf-card batches call
@@ -95,26 +105,29 @@ fn tree_offset(
// A one-yard bush still needs a crown; tree leaf cards generally begin above this range and
// therefore reach full weight, while the first 0.15 yd remains a planted base.
let crown = smoothstep(0.15, 1.5, h);
- let weight = crown * crown;
+ // MONKEY (fix-wind): a taller crown swings further (lever arm): 0.43x at 1.5 yd, 1x at 6 yd,
+ // capped at 1.3x from 8.5 yd, so a bush nods while a canopy sways.
+ let weight = crown * crown * clamp(0.25 + h * 0.12, 0.25, 1.3);
let dir = normalize(frame.wind_a.xy + vec2(1.0e-6, 0.0));
let cross = vec2(-dir.y, dir.x);
let base = wind_base_dir(frame);
let base_cross = vec2(-base.y, base.x);
let phase = fract(sin(dot(anchor.xz, vec2(0.173, 0.317))) * 43758.5453) * WIND_TAU;
- // 0.08 rad/yd sway, 0.61x side wave and a 2.3 rad/s-at-base-speed flutter, as whole cycles
- // per wrap of travel: 52, 32 and 167.
+ // MONKEY (fix-wind): visible tuning. At the 9 yd/s base speed: a 4 s crown sway (114 cycles
+ // per wrap), a 5.5 s side wave (82) and a 0.8 s leaf flutter (569).
let slow = sin(dot(base, world.xz) * (WIND_TAU / 34.0)
- - wind_travel_phase(frame.wind_b.x, 52.0) + phase);
+ - wind_travel_phase(frame.wind_b.x, 114.0) + phase);
let side = sin(dot(base_cross, world.xz) * (WIND_TAU / 21.0)
- - wind_travel_phase(frame.wind_b.x, 32.0) + phase * 1.7);
- let flutter = sin(wind_travel_phase(frame.wind_b.x, 167.0)
+ - wind_travel_phase(frame.wind_b.x, 82.0) + phase * 1.7);
+ let flutter = sin(wind_travel_phase(frame.wind_b.x, 569.0)
+ dot(world.xz, vec2(1.7, 2.1)) + phase * 2.0);
let gust = 0.75 + frame.wind_a.w * 0.5;
let fade = 1.0 / (1.0 + distance(camera, world) * 0.006);
let xz = (
- dir * (0.045 + slow * 0.055)
- + cross * side * 0.025
- + dir * flutter * 0.012
- ) * weight * gust * fade * frame.wind_b.y * frame.wind_b.w;
- return vec3(xz.x, 0.0, xz.y);
+ dir * (0.08 + slow * 0.20)
+ + cross * side * 0.08
+ + dir * flutter * 0.03
+ ) * weight * gust * mix(0.75, 1.05, (wind_gust_roll(frame, anchor.xz) - 0.55) / 0.65)
+ * fade * frame.wind_b.y * frame.wind_b.w;
+ return vec3(xz.x, -0.15 * dot(xz, xz), xz.y);
}
diff --git a/crates/benilla-world/src/wind/mod.rs b/crates/benilla-world/src/wind/mod.rs
index 71fe4b45..1f88610c 100644
--- a/crates/benilla-world/src/wind/mod.rs
+++ b/crates/benilla-world/src/wind/mod.rs
@@ -91,6 +91,37 @@ impl FoliageWind {
}
}
+/// MONKEY (fix-wind): the player's foliage sway strength (`foliageWindStrength`), a plain gain on
+/// the sway amplitude: 0.25..3, 1 = the shipped tuning. Parting around units ignores it.
+#[derive(Resource, Clone, Copy, Debug, PartialEq)]
+pub struct FoliageWindStrength(pub f32);
+
+impl FoliageWindStrength {
+ pub const MIN: f32 = 0.25;
+ pub const MAX: f32 = 3.0;
+ /// The registered default.
+ pub const REGISTERED: f32 = 1.0;
+
+ /// A CVar value into the slider's range (non-finite reads as the default).
+ pub fn from_cvar(v: f32) -> Self {
+ Self(if v.is_finite() {
+ v.clamp(Self::MIN, Self::MAX)
+ } else {
+ Self::REGISTERED
+ })
+ }
+}
+
+impl Default for FoliageWindStrength {
+ fn default() -> Self {
+ Self(Self::REGISTERED)
+ }
+}
+
+/// MONKEY (fix-wind): a storm swings the foliage harder, up to +60 % at a full storm, on top of the
+/// faster travel the storm's wind speed already gives.
+pub const STORM_SWAY_GAIN: f32 = 0.6;
+
impl Default for FoliageWind {
fn default() -> Self {
// The resource exists before the CVar host. Its ordinary default therefore has to equal
@@ -186,6 +217,7 @@ fn update_wind(
time: Res