From b2b5ef7539158fb4cdc150cd5efe2c46e6247a5b Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Sat, 26 Sep 2026 00:42:27 +0100
Subject: [PATCH] =?UTF-8?q?MONKEY=20(reviewfix-a):=20cloud-review=20fixes?=
=?UTF-8?q?=20=E2=80=94=20cvars,=20capture=20fixture,=20wind=20pin,=20post?=
=?UTF-8?q?=20perf?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- farclip High seed 777 declared in SEEDED_DEVIATIONS (owner: High is the
default); test walks the seeded column against every reference row (#6)
- WOW_CAPTURE_CVARS= replaces BENILLA_HOME as the capture cvar fixture (#13)
- label derivation skips session-owned rows, so an env lever no longer
persists graphicsQuality/lightingQuality = Custom (#14)
- /console help strings: restored line continuations + a test (#15)
- WOW_FOLIAGE_WIND read only in a dev build under WOW_CAPTURE (#16)
- fog node skipped at volumetricFog 0 with no lamp; fog and SSAO uniforms
persistent per view, written in prepare (#17)
- tests: every governed graphics row through its observer; OptionsFrame
farclip slider tied to view::FARCLIP_RANGE
Co-Authored-By: Claude Opus 5.5
---
crates/benilla-app/src/cvars.rs | 197 ++++++++++++++++--
crates/benilla-app/src/monkey_gfx.rs | 23 +-
crates/benilla-app/src/ssao.rs | 33 ++-
.../src/ui_script/options_tests.rs | 26 +++
crates/benilla-app/src/volumetric_fog.rs | 46 +++-
crates/benilla-app/src/zone_skybox.rs | 4 +-
crates/benilla-world/src/wind/mod.rs | 28 ++-
7 files changed, 310 insertions(+), 47 deletions(-)
diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs
index 2e31b997..8e6286cb 100644
--- a/crates/benilla-app/src/cvars.rs
+++ b/crates/benilla-app/src/cvars.rs
@@ -955,7 +955,8 @@ pub(crate) const REGISTERED: &[Registered] = &[
ours(
"skyQuality",
"0",
- "benilla's own: sky quality, 0 Classic / 1 Enhanced (smooth gradient, sun glow, stars) / \n 2 High (+ detailed sun-lit clouds)",
+ "benilla's own: sky quality, 0 Classic / 1 Enhanced (smooth gradient, sun glow, stars) / \
+ 2 High (+ detailed sun-lit clouds)",
),
// MONKEY (wind): one tier controls the grass-only and grass-plus-tree receivers.
ours(
@@ -984,7 +985,8 @@ pub(crate) const REGISTERED: &[Registered] = &[
ours(
"zoneSkyboxes",
"0",
- "benilla's own: draw the zone skybox LightParams names for the living, 0 Off / 1 On; the reference draws a DBC skybox only for the ghost",
+ "benilla's own: draw the zone skybox LightParams names for the living, 0 Off / 1 On; the \
+ reference draws a DBC skybox only for the ghost",
),
ours(
"waterQuality",
@@ -1133,13 +1135,15 @@ pub(crate) const REGISTERED: &[Registered] = &[
ours(
"exteriorShadows",
"1",
- "benilla's own: outdoor fire lights (campfires, braziers, lampposts) cast real shadows at night; no effect by day",
+ "benilla's own: outdoor fire lights (campfires, braziers, lampposts) cast real shadows at \
+ night; no effect by day",
),
// MONKEY (daylight: terrain torch casters): the ground as a torch caster.
ours(
"torchTerrainShadows",
"0",
- "benilla's own: the ground casts into outdoor fire shadows (hills and banks block a fire's light); needs exteriorShadows",
+ "benilla's own: the ground casts into outdoor fire shadows (hills and banks block a fire's \
+ light); needs exteriorShadows",
),
// MONKEY (static torch cache): residency and per-frame work have separate live budgets.
ours(
@@ -2334,8 +2338,11 @@ fn same_value(a: &str, b: &str) -> bool {
/// allowed to say, which is what keeps the row from going stale behind a `/console` write.
pub(crate) fn derive_lighting_quality(cvars: &Cvars) -> &'static str {
for (name, members) in LIGHTING_PRESETS {
+ // MONKEY (reviewfix-a): a session-owned row (an env lever) is not the player's choice and
+ // must not turn the saved label Custom; it is skipped, as the seed skips it.
let matched = members
.iter()
+ .filter(|(k, _)| !cvars.is_session_owned(k))
.all(|(k, v)| cvars.get(k).is_some_and(|live| same_value(live, v)));
if matched {
return name;
@@ -2379,6 +2386,21 @@ pub(crate) const GRAPHICS_PRESET_NAMES: [&str; 5] = ["Classic", "Low", "Medium",
/// The rung a player with no saved preset boots into ([`Cvars::seed_graphics_preset`]).
pub(crate) const GRAPHICS_DEFAULT: &str = "High";
+/// MONKEY (reviewfix-a): **the rows where the first-boot seed leaves the reference** — the
+/// [`GRAPHICS_DEFAULT`] column value a new player actually boots with, against the reference's
+/// own default. The registered row keeps its `same(...)` (the registry default IS the
+/// reference's; a capture or a test, which never seeds, runs at it), so this list is where the
+/// deviation of the *seeded* boot value is declared, `Deviates`-style: `(row, reference, why)`.
+/// `seeded_column_deviates_only_where_declared` walks the whole column against the reference
+/// rows, both ways.
+#[cfg_attr(not(test), allow(dead_code))]
+pub(crate) const SEEDED_DEVIATIONS: &[(&str, &str, &str)] = &[(
+ "farclip",
+ "350",
+ "owner decision: the High preset is the first-boot default, and High's view distance is the \
+ reference's own slider maximum, 777 yd; Classic keeps 350",
+)];
+
/// **The one place the Graphics Preset is written down**: one line per governed row, one column
/// per rung of [`GRAPHICS_PRESET_NAMES`]. `lightingQuality` is itself a row — writing its rung
/// writes [`LIGHTING_PRESETS`]' members — so every lighting, water, fog, post and wind row the
@@ -2421,8 +2443,10 @@ fn graphics_column(name: &str) -> Option {
/// ladders show). Reads `lightingQuality`'s DERIVED label, so run it after that is mirrored.
pub(crate) fn derive_graphics_quality(cvars: &Cvars) -> &'static str {
for (col, name) in GRAPHICS_PRESET_NAMES.iter().enumerate() {
+ // MONKEY (reviewfix-a): session-owned rows are skipped, as in the lighting ladder above.
let matched = GRAPHICS_PRESETS
.iter()
+ .filter(|(k, _)| !cvars.is_session_owned(k))
.all(|(k, v)| cvars.get(k).is_some_and(|live| same_value(live, v[col])));
if matched {
return name;
@@ -2636,14 +2660,14 @@ fn stored_config() -> StoredConfig {
}
}
-/// MONKEY (integration): a capture may opt into one explicit, read-only CVar fixture through
-/// `BENILLA_HOME`. All other local state stays hermetic and [`save_config`] still sees no path,
-/// so deterministic A/B homes can select graphics tiers without a capture writing anything back.
+/// MONKEY (integration): a capture may opt into one explicit, read-only CVar fixture.
+/// MONKEY (reviewfix-a): through its own variable, `WOW_CAPTURE_CVARS=`, not
+/// `BENILLA_HOME` (the general local-state override a player shell may carry, which would load
+/// that player's settings into a baseline). Without it a capture reads no config at all. All
+/// other local state stays hermetic and [`save_config`] still sees no path.
fn config_read_path() -> Option {
if std::env::var_os("WOW_CAPTURE").is_some() {
- return std::env::var_os("BENILLA_HOME")
- .map(std::path::PathBuf::from)
- .map(|home| home.join("config.toml"));
+ return std::env::var_os("WOW_CAPTURE_CVARS").map(std::path::PathBuf::from);
}
crate::local_state::config_path()
}
@@ -3740,19 +3764,36 @@ mod tests {
/// row's rung values do too.
#[test]
fn graphics_rows_pass_their_observers_unclamped() {
+ // MONKEY (reviewfix-a): every governed row, every rung — not only `farclip`. Each value
+ // is driven through the real setter and read back off the resource its renderer reads.
let mut app = cvar_app();
for (col, name) in GRAPHICS_PRESET_NAMES.iter().enumerate() {
- let farclip = GRAPHICS_PRESETS
- .iter()
- .find(|(k, _)| *k == "farclip")
- .unwrap()
- .1[col];
- apply(&mut app, "farclip", farclip);
- assert_eq!(
- res::(&app).farclip,
- farclip.parse::().unwrap(),
- "{name}"
- );
+ for (k, values) in GRAPHICS_PRESETS {
+ if *k == "lightingQuality" {
+ continue; // its members are welded by the lighting-preset census test
+ }
+ let value = values[col];
+ apply(&mut app, k, "-1");
+ apply(&mut app, k, value);
+ let video = res::(&app);
+ let flag = |b: bool| if b { 1.0 } else { 0.0 };
+ let applied = match *k {
+ "farclip" => res::(&app).farclip,
+ "skyQuality" => video.sky_quality as f32,
+ "skyDither" => flag(res::(&app).0),
+ "fogModel" => {
+ flag(res::(&app).modern())
+ }
+ "rainSurfaces" => flag(res::(&app).0),
+ "torchTerrainShadows" => flag(video.torch_terrain_shadows),
+ "daylightWindowSplit" => flag(video.daylight_window_split),
+ "ambientOcclusion" => video.ambient_occlusion as f32,
+ "zoneSkyboxes" => flag(res::(&app).0),
+ "lampFog" => video.lamp_fog as f32,
+ _ => panic!("add the observer readback for {k}"),
+ };
+ assert_eq!(applied, value.parse::().unwrap(), "{name}/{k}");
+ }
}
}
@@ -4382,6 +4423,12 @@ mod tests {
app.init_resource::(); // MONKEY (wet)
app.add_observer(crate::monkey_gfx::on_cvar);
},
+ // MONKEY (reviewfix-a): the zone-skybox bridge, so every governed row has its observer.
+ |app| {
+ app.insert_resource(crate::zone_skybox::ZoneSkyboxOverride(None));
+ app.init_resource::();
+ app.add_observer(crate::zone_skybox::on_cvar);
+ },
|app| {
app.add_observer(crate::player::camera::on_cvar);
},
@@ -4957,6 +5004,8 @@ mod tests {
)
.unwrap();
let _h = EnvGuard::set("BENILLA_HOME", tmp.to_str().unwrap());
+ // MONKEY (reviewfix-a): the fixture is named by its own variable, not BENILLA_HOME.
+ let _x = EnvGuard::set("WOW_CAPTURE_CVARS", tmp.join("config.toml").to_str().unwrap());
let _c = EnvGuard::set("WOW_CAPTURE", "post-lava-searing");
assert_eq!(boot_cvar("bloom").as_deref(), Some("0"));
@@ -5073,4 +5122,110 @@ mod tests {
]
);
}
+
+ // ─── MONKEY (reviewfix-a) ────────────────────────────────────────────────────────────────
+
+ /// The value the reference boots a row at, as its [`Reference`] column records it; `None`
+ /// for benilla's own rows (nothing to match).
+ fn reference_boot_value(row: &Registered) -> Option<&'static str> {
+ match &row.reference {
+ Reference::Same(v) => Some(v),
+ // The reference's own boot code lands where our default does.
+ Reference::Overridden { .. } => Some(row.default),
+ Reference::Deviates { value, .. } => Some(value),
+ Reference::Ours(_) => None,
+ }
+ }
+
+ /// **The seeded High column against every reference row** (review #6): a governed row a new
+ /// player boots off the reference's value must be declared in [`SEEDED_DEVIATIONS`] with
+ /// that reference value and a reason, and a declared row must really deviate.
+ #[test]
+ fn seeded_column_deviates_only_where_declared() {
+ let col = graphics_column(GRAPHICS_DEFAULT).unwrap();
+ for (k, values) in GRAPHICS_PRESETS {
+ if k.eq_ignore_ascii_case("lightingQuality") {
+ continue; // High on that ladder IS the registered defaults (its own test)
+ }
+ let row = REGISTERED.iter().find(|r| r.name == *k).expect("governed row registered");
+ let declared = SEEDED_DEVIATIONS.iter().find(|(n, _, _)| n == k);
+ match reference_boot_value(row) {
+ Some(reference) if !same_value(values[col], reference) => {
+ let (_, value, why) = declared.unwrap_or_else(|| {
+ panic!(
+ "{k}: the seeded {GRAPHICS_DEFAULT} value {} leaves the reference's \
+ {reference}; declare it in SEEDED_DEVIATIONS",
+ values[col]
+ )
+ });
+ assert!(same_value(value, reference), "{k}: declared reference is stale");
+ assert!(!why.trim().is_empty(), "{k}: a deviation owes a reason");
+ }
+ _ => assert!(
+ declared.is_none(),
+ "{k}: declared in SEEDED_DEVIATIONS but the seed does not deviate"
+ ),
+ }
+ }
+ for (n, _, _) in SEEDED_DEVIATIONS {
+ assert!(
+ GRAPHICS_PRESETS.iter().any(|(k, _)| k == n),
+ "{n}: declared but not governed"
+ );
+ }
+ }
+
+ /// Review #14: an env lever owns a governed row for the session; the label must not derive
+ /// (and so persist) `Custom` because of it.
+ #[test]
+ fn a_session_owned_row_does_not_derive_custom() {
+ let mut cvars = fresh_registry();
+ apply_graphics_preset(&mut cvars, "High");
+ lighting_quality(&mut cvars);
+ assert_eq!(derive_graphics_quality(&cvars), "High");
+ cvars.own_for_session("farclip", Some("500"));
+ assert_eq!(cvars.get("farclip"), Some("500"));
+ assert_eq!(derive_graphics_quality(&cvars), "High");
+ // A lighting member owned by the session is skipped the same way.
+ cvars.own_for_session("waterQuality", Some("0"));
+ assert_eq!(derive_lighting_quality(&cvars), "High");
+ }
+
+ /// Review #15: help strings are one line — a lost `\` continuation leaves a literal `\n` or a
+ /// run of indentation spaces in the `/console` text.
+ #[test]
+ fn help_strings_have_no_broken_continuations() {
+ for row in REGISTERED {
+ let why = match &row.reference {
+ Reference::Ours(why)
+ | Reference::Deviates { why, .. }
+ | Reference::Overridden { why, .. } => *why,
+ Reference::Same(_) => continue,
+ };
+ assert!(
+ !why.contains('\n') && !why.contains("\\n") && !why.contains(" "),
+ "{}: broken line continuation in {why:?}",
+ row.name
+ );
+ }
+ }
+
+ /// Review #13: a capture reads CVars only from the dedicated `WOW_CAPTURE_CVARS` fixture,
+ /// never from a `BENILLA_HOME` the shell happens to carry.
+ #[test]
+ fn a_capture_reads_cvars_only_from_its_fixture() {
+ use crate::local_state::test_env::{EnvGuard, ENV_LOCK};
+ let _l = ENV_LOCK
+ .lock()
+ .unwrap_or_else(std::sync::PoisonError::into_inner);
+ let _c = EnvGuard::set("WOW_CAPTURE", "overlook-noon");
+ let _h = EnvGuard::set("BENILLA_HOME", "player-home");
+ let _x = EnvGuard::unset("WOW_CAPTURE_CVARS");
+ assert_eq!(config_read_path(), None);
+ let _x = EnvGuard::set("WOW_CAPTURE_CVARS", "fixture/config.toml");
+ assert_eq!(
+ config_read_path(),
+ Some(std::path::PathBuf::from("fixture/config.toml"))
+ );
+ }
}
diff --git a/crates/benilla-app/src/monkey_gfx.rs b/crates/benilla-app/src/monkey_gfx.rs
index 4ba49e2a..8b40b233 100644
--- a/crates/benilla-app/src/monkey_gfx.rs
+++ b/crates/benilla-app/src/monkey_gfx.rs
@@ -33,12 +33,9 @@ pub(crate) fn on_cvar(
}
// MONKEY (wind): numeric tier, live; 0 is the exact no-displacement path.
"foliagewind" => {
- // Capture-only A/B override. Normal runs have no such environment variable and obey
- // the live CVar exactly; the instrument can force Off without rewriting Config.wtf.
- let requested = std::env::var("WOW_FOLIAGE_WIND")
- .ok()
- .and_then(|v| v.parse::().ok())
- .unwrap_or_else(|| ev.num());
+ // Capture-only A/B pin (MONKEY reviewfix-a: gated like `capture_daylight` — dev
+ // build AND `WOW_CAPTURE`). Player runs obey the live CVar exactly.
+ let requested = foliage_wind_pin().map(f32::from).unwrap_or_else(|| ev.num());
let want = requested.clamp(0.0, 2.0) as u8;
if foliage_wind.0 != want {
foliage_wind.0 = want;
@@ -57,6 +54,14 @@ pub(crate) fn on_cvar(
}
}
+/// MONKEY (reviewfix-a): the `WOW_FOLIAGE_WIND` capture pin, only in a dev build under `WOW_CAPTURE`.
+fn foliage_wind_pin() -> Option {
+ if !crate::run_mode::dev_affordances() {
+ return None;
+ }
+ FoliageWind::capture_override()
+}
+
/// Registers the programme's cvar bridges. Must be added before `CvarPlugin` so the saved values
/// applied at Startup reach it (the edge `game_plugins.rs` documents for every knob plugin).
pub(crate) struct MonkeyGfxPlugin;
@@ -64,7 +69,11 @@ pub(crate) struct MonkeyGfxPlugin;
impl Plugin for MonkeyGfxPlugin {
fn build(&self, app: &mut App) {
app.init_resource::()
- .init_resource::()
+ // MONKEY (reviewfix-a): the resource is seeded here so a player build never takes the
+ // capture pin (the world plugin's `init_resource` keeps whatever is already present).
+ .insert_resource(FoliageWind(
+ foliage_wind_pin().unwrap_or(FoliageWind::REGISTERED),
+ ))
.init_resource::()
.init_resource::() // MONKEY (wet)
.add_observer(on_cvar);
diff --git a/crates/benilla-app/src/ssao.rs b/crates/benilla-app/src/ssao.rs
index a7803742..0c950771 100644
--- a/crates/benilla-app/src/ssao.rs
+++ b/crates/benilla-app/src/ssao.rs
@@ -129,6 +129,8 @@ impl Plugin for AmbientOcclusionPlugin {
(
prepare_pipelines.in_set(RenderSystems::Prepare),
prepare_textures.in_set(RenderSystems::PrepareResources),
+ // MONKEY (reviewfix-a): one uniform per view, rewritten, not allocated per frame.
+ prepare_ao_uniforms.in_set(RenderSystems::PrepareResources),
),
)
.add_render_graph_node::>(Core3d, AoLabel)
@@ -357,6 +359,31 @@ fn prepare_textures(
#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)]
struct AoLabel;
+/// MONKEY (reviewfix-a): the view's AO uniform, created once and rewritten in prepare.
+#[derive(Component)]
+struct AoUniform(UniformBuffer);
+
+fn prepare_ao_uniforms(
+ mut commands: Commands,
+ device: Res,
+ queue: Res,
+ mut views: Query<(Entity, &AoView, Option<&mut AoUniform>)>,
+) {
+ for (entity, settings, uniform) in &mut views {
+ match uniform {
+ Some(mut uniform) => {
+ uniform.0.set(*settings);
+ uniform.0.write_buffer(&device, &queue);
+ }
+ None => {
+ let mut uniform = UniformBuffer::from(*settings);
+ uniform.write_buffer(&device, &queue);
+ commands.entity(entity).insert(AoUniform(uniform));
+ }
+ }
+ }
+}
+
#[derive(Default)]
struct AoNode;
@@ -365,7 +392,7 @@ impl ViewNode for AoNode {
&'static ViewTarget,
&'static ViewDepthTexture,
&'static ViewUniformOffset,
- &'static AoView,
+ &'static AoUniform,
&'static ViewAoPipelines,
&'static AoTextures,
);
@@ -397,9 +424,7 @@ impl ViewNode for AoNode {
let layouts = world.resource::();
let ms = (depth.texture.sample_count() > 1) as usize;
let device = context.render_device().clone();
- let mut uniform = UniformBuffer::from(*settings);
- uniform.write_buffer(&device, world.resource::());
- let params = uniform.binding().unwrap();
+ let params = settings.0.binding().unwrap();
// `main_texture_view` is the single-sample texture the opaque pass resolved into.
let ao_bind = device.create_bind_group(
diff --git a/crates/benilla-app/src/ui_script/options_tests.rs b/crates/benilla-app/src/ui_script/options_tests.rs
index 4456997a..e577cae2 100644
--- a/crates/benilla-app/src/ui_script/options_tests.rs
+++ b/crates/benilla-app/src/ui_script/options_tests.rs
@@ -5732,3 +5732,29 @@ fn the_guild_line_greys_with_player_names_and_the_follow_speed_with_the_style()
assert!(speed_thumb(&mut s));
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
+
+/// MONKEY (reviewfix-a): the Advanced Graphics farclip slider's range is `view::FARCLIP_RANGE`,
+/// and its 60-yd grid lands exactly on the maximum (the 1.12 grid the page advertises).
+#[test]
+fn farclip_slider_range_is_the_view_range() {
+ let xml = include_str!("../../assets/ui/OptionsFrame.xml");
+ let row = xml
+ .split(" = args
+ .split(',')
+ .take(3)
+ .map(|v| v.trim().parse().expect("numeric slider bound"))
+ .collect();
+ let range = benilla_world::view::FARCLIP_RANGE;
+ assert_eq!(nums[0], *range.start(), "slider min");
+ assert_eq!(nums[1], benilla_world::view::FARCLIP_MAX, "slider max");
+ assert_eq!(nums[1], *range.end());
+ assert_eq!((nums[1] - nums[0]) % nums[2], 0.0, "the step lands on the maximum");
+}
diff --git a/crates/benilla-app/src/volumetric_fog.rs b/crates/benilla-app/src/volumetric_fog.rs
index 2d608273..52448ece 100644
--- a/crates/benilla-app/src/volumetric_fog.rs
+++ b/crates/benilla-app/src/volumetric_fog.rs
@@ -135,7 +135,14 @@ impl Plugin for VolumetricFogPlugin {
.insert_resource(FogShader(shader))
.init_resource::>()
.add_systems(RenderStartup, init_pipeline)
- .add_systems(Render, prepare_pipelines.in_set(RenderSystems::Prepare))
+ .add_systems(
+ Render,
+ (
+ prepare_pipelines.in_set(RenderSystems::Prepare),
+ // MONKEY (reviewfix-a): one uniform per view, rewritten, not allocated per frame.
+ prepare_fog_uniforms.in_set(RenderSystems::PrepareResources),
+ ),
+ )
.add_render_graph_node::>(Core3d, FogLabel)
.add_render_graph_edges(
Core3d,
@@ -289,6 +296,12 @@ fn update_fog(
} else {
FogLamps::default()
};
+ // MONKEY (reviewfix-a): no haze tier and no lamp in range is an identity pass — skip the
+ // node (and its depth bind and ping-pong) rather than run it every frame.
+ if tier == 0 && lamps.count == 0 {
+ commands.entity(entity).remove::();
+ continue;
+ }
let fog = Vec3::from_array(lighting.fog_color);
// MONKEY (fog): rows 1-3 as packed for the light buffer.
let rows = monkey.as_ref().map_or([[0.0; 4]; 16], |m| m.pack(0.0, 0.0));
@@ -323,6 +336,31 @@ fn update_fog(
#[derive(Resource)]
struct FogShader(Handle);
+
+/// MONKEY (reviewfix-a): the view's fog uniform, created once and rewritten in prepare.
+#[derive(Component)]
+struct FogUniform(UniformBuffer);
+
+fn prepare_fog_uniforms(
+ mut commands: Commands,
+ device: Res,
+ queue: Res,
+ mut views: Query<(Entity, &FogView, Option<&mut FogUniform>)>,
+) {
+ for (entity, fog, uniform) in &mut views {
+ match uniform {
+ Some(mut uniform) => {
+ uniform.0.set(*fog);
+ uniform.0.write_buffer(&device, &queue);
+ }
+ None => {
+ let mut uniform = UniformBuffer::from(*fog);
+ uniform.write_buffer(&device, &queue);
+ commands.entity(entity).insert(FogUniform(uniform));
+ }
+ }
+ }
+}
#[derive(Resource)]
struct FogPipeline {
layouts: [BindGroupLayoutDescriptor; 2],
@@ -441,7 +479,7 @@ impl ViewNode for FogNode {
&'static ViewUniformOffset,
&'static ViewLightsUniformOffset,
&'static ViewShadowBindings,
- &'static FogView,
+ &'static FogUniform,
&'static ViewFogPipeline,
);
fn run<'w>(
@@ -474,8 +512,6 @@ impl ViewNode for FogNode {
};
let settings = world.resource::();
let device = context.render_device();
- let mut uniform = UniformBuffer::from(*fog);
- uniform.write_buffer(device, world.resource::());
let out = target.post_process_write();
let bind = device.create_bind_group(
"gamma_fog",
@@ -489,7 +525,7 @@ impl ViewNode for FogNode {
lights,
&shadows.directional_light_depth_texture_view,
&settings.comparison,
- uniform.binding().unwrap(),
+ fog.0.binding().unwrap(),
)),
);
let diagnostics = context.diagnostic_recorder();
diff --git a/crates/benilla-app/src/zone_skybox.rs b/crates/benilla-app/src/zone_skybox.rs
index ce4342cc..2ae603c0 100644
--- a/crates/benilla-app/src/zone_skybox.rs
+++ b/crates/benilla-app/src/zone_skybox.rs
@@ -6,7 +6,7 @@ use bevy::prelude::*;
/// The session override, read once.
#[derive(Resource, Clone, Copy)]
-struct ZoneSkyboxOverride(Option);
+pub(crate) struct ZoneSkyboxOverride(pub(crate) Option); // MONKEY (reviewfix-a): test census
pub(crate) struct ZoneSkyboxPlugin;
@@ -31,7 +31,7 @@ fn apply_override(
}
}
-fn on_cvar(
+pub(crate) fn on_cvar(
ev: On,
forced: Res,
zone: Option>,
diff --git a/crates/benilla-world/src/wind/mod.rs b/crates/benilla-world/src/wind/mod.rs
index 47676254..668e0b5f 100644
--- a/crates/benilla-world/src/wind/mod.rs
+++ b/crates/benilla-world/src/wind/mod.rs
@@ -74,17 +74,29 @@ impl Default for WindField {
#[derive(Resource, Clone, Copy, Debug, PartialEq, Eq)]
pub struct FoliageWind(pub u8);
+impl FoliageWind {
+ /// The registered `foliageWind` default (grass + trees).
+ pub const REGISTERED: u8 = 2;
+
+ /// MONKEY (reviewfix-a): the capture-only A/B pin `WOW_FOLIAGE_WIND`, read only under
+ /// `WOW_CAPTURE` (like the other capture levers) so a player run always obeys the saved CVar.
+ /// The app adds the dev-build gate on top.
+ pub fn capture_override() -> Option {
+ std::env::var_os("WOW_CAPTURE")?;
+ std::env::var("WOW_FOLIAGE_WIND")
+ .ok()
+ .and_then(|v| v.trim().parse::().ok())
+ .filter(|v| v.is_finite())
+ .map(|v| v.clamp(0.0, 2.0) as u8)
+ }
+}
+
impl Default for FoliageWind {
fn default() -> Self {
// The resource exists before the CVar host. Its ordinary default therefore has to equal
- // the registered default; a default-valued CVar emits no change event at boot. The env
- // arm is capture-only and lets the A/B harness force the exact zero-displacement path.
- let tier = std::env::var("WOW_FOLIAGE_WIND")
- .ok()
- .and_then(|v| v.parse::().ok())
- .unwrap_or(2.0)
- .clamp(0.0, 2.0) as u8;
- Self(tier)
+ // the registered default; a default-valued CVar emits no change event at boot. The
+ // capture pin lets the A/B harness force the exact zero-displacement path.
+ Self(Self::capture_override().unwrap_or(Self::REGISTERED))
}
}