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)) } }