MONKEY (integration): merge gfx/lampfog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-25 17:36:40 +01:00
co-authored by Claude Opus 5.5
10 changed files with 590 additions and 101 deletions
+4
View File
@@ -33,6 +33,7 @@ behind each constant.
| 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; phase 1 has no rain-occlusion map, so shelter is classified only by interior/unlit state and surface facing | 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`, `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`; 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 (`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..4` diagnostic views (factor, protection, distance, raw occlusion), `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` |
| Night and interior level | global dimming of the night sky term and of interior ambient | `nightGain`, `interiorGain`, `interiorBakeFloor` |
Players reach all of it from **Options -> Advanced Graphics** (a Lighting Quality preset Off / Low / Medium / High plus the individual rows; Off is the original client look). The dev build has a panel for all of it: **Ctrl+Shift+D → Lighting & shadows**, with Dim / Default /
@@ -98,6 +99,9 @@ is its own module, documented in `WATER.md`.
- World lights use upstream's `WorldPointLight`, never Bevy's `PointLight`.
- Foliage wind is vertex-only. Tree and leaf shadow meshes stay static; the small mismatch is the
accepted first-stage cost/complexity tradeoff.
- MONKEY (lampfog): `ResolvedPointLights` is a CPU-only read view of the live, truncated point table;
it does not add a row to the shared GPU buffer. Lamp selection is capped at 100 yd and ranks both
exterior and interior rows together before the 16/32 nearest limit is applied.
- WGSL only fails at pipeline creation, so a shader edit is verified by running a world scene, not
by `cargo check`.
- Exterior point lights are selected per draw unit (12 slots, ranked against the chunk's box); the
@@ -538,6 +538,8 @@
windTrees = "Grass + Trees",
-- MONKEY (ao): localised contact-shadow row.
ambientOcclusion = "Ambient Occlusion",
-- MONKEY (lampfog)
lampFog = "Lamp Fog",
yards = "%d yd",
tips = {
LIGHTING_QUALITY =
@@ -677,6 +679,12 @@
.. "High is smoother and reaches a little further.\n\n"
.. "Fades out with distance and spares the sky, water, lit windows and flames. "
.. "Off leaves the image unchanged.",
-- MONKEY (lampfog)
LAMP_FOG =
"Makes nearby lamps and fires glow through the air after dark. Low uses the "
.. "nearest 16 lights; High uses 32. Rain and fog strengthen the halos. "
.. "The effect is unshadowed, but stops at visible scene depth.\n\n"
.. "Off is unchanged, and daylight is unchanged at every setting.",
},
},
ruRU = {
@@ -728,6 +736,8 @@
windTrees = "Трава + деревья",
-- MONKEY (ao): Russian contact-shadow row.
ambientOcclusion = "Затенение окружения",
-- MONKEY (lampfog)
lampFog = "Свечение фонарей в тумане",
yards = "%d ярд.",
tips = {
LIGHTING_QUALITY =
@@ -879,6 +889,12 @@
.. "«Высокое» — более плавное и захватывает чуть больше.\n\n"
.. "Ослабевает с расстоянием и не затрагивает небо, воду, освещённые окна и пламя. "
.. "«Выкл» оставляет изображение без изменений.",
-- MONKEY (lampfog)
LAMP_FOG =
"После заката ближайшие фонари и огни светятся сквозь воздух. Низкое "
.. "качество использует 16 ближайших источников, высокое — 32. Дождь и туман усиливают ореолы. "
.. "Эффект не рассчитывает тени, но обрывается на видимой глубине.\n\n"
.. "Выключено — без изменений; днём изображение не меняется при любом значении.",
},
},
}
@@ -953,6 +969,8 @@
"RowFoliageWind",
-- MONKEY (ao): appended so the other rows keep their indices.
"RowAmbientOcclusion",
-- MONKEY (lampfog)
"RowLampFog",
},
-- Player Guild Names is a child of Player Names, as in 1.12 (l.702-708).
Nameplates = {
@@ -3118,6 +3136,22 @@
})
</OnLoad></Scripts>
</Frame>
<!-- MONKEY (lampfog): night point-light airlight tiers. -->
<Frame name="$parentRowLampFog" inherits="OptionsDropdownRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowAmbientOcclusion" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAmbientOcclusion" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "lampFog", BENILLA_ADVGFX.lampFog,
"BENILLA_TOOLTIP_LAMP_FOG")
OptionsDropdownRow_Setup(self, {
{ text = BENILLA_ADVGFX.fogOff, value = "0" },
{ text = BENILLA_ADVGFX.qLow, value = "1" },
{ text = BENILLA_ADVGFX.qHigh, value = "2" },
})
</OnLoad></Scripts>
</Frame>
</Frames>
<!-- The poll (see AdvGfx_OnUpdate): this is the one page whose
settings move from outside it — the Lighting Quality row
@@ -1665,6 +1665,44 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
eye: [-9495.2, 60.8, 59.0], look: [-9500.00, 56.00, 56.9], minute: 720,
ui: Some(UiFixture::Subject { kind: SubjectKind::Creature, at: SUBJECT_SUN }),
},
// MONKEY (lampfog): lampFog 0/2 A/B set. Kept at the END so parallel lane tables merge cleanly.
// Goldshire square: the Lion's Pride fixtures and outdoor lamps share one night frame.
Scenario {
name: "lampfog-goldshire-night",
map: Some(MAP_AZEROTH),
eye: [-9460.0, 70.0, 58.0],
look: [-9450.0, 20.0, 61.0],
minute: 0,
ui: None,
},
// Stormwind Trade District from the established city-scale camera, after dark.
Scenario {
name: "lampfog-stormwind-night",
map: Some(MAP_AZEROTH),
eye: [-8833.38, 628.63, 96.0],
look: [-8809.1, 672.3, 94.0],
minute: 0,
ui: None,
},
// Goldshire's east road, looking through the known exterior lamp at (-9477, 53, 60). This is
// deliberately not the fence instrument: that camera looks down at an unlit verge at night.
Scenario {
name: "lampfog-elwynn-road-night",
map: Some(MAP_AZEROTH),
eye: [-9505.0, 36.0, 61.5],
look: [-9455.0, 75.0, 59.0],
minute: 0,
ui: None,
},
// Exact Goldshire framing at noon: lampFog 0 and 2 must be pixel-identical.
Scenario {
name: "lampfog-goldshire-day",
map: Some(MAP_AZEROTH),
eye: [-9460.0, 70.0, 58.0],
look: [-9450.0, 20.0, 61.0],
minute: 720,
ui: None,
},
];
/// MONKEY (p0 baseline): the Elwynn programme view, above the canopy east of Goldshire looking
+13 -5
View File
@@ -921,6 +921,12 @@ pub(crate) const REGISTERED: &[Registered] = &[
),
// MONKEY (volumetric fog): saved live tier; capture override stays session-only.
ours("volumetricFog", "1", "benilla's own: near-field volumetric fog, 0 Off / 1 Low / 2 High"),
// MONKEY (lampfog): opt-in point-light halos; High graphics preset value is 2.
ours(
"lampFog",
"0",
"benilla's own: lamps scattering through night fog, 0 Off / 1 Low (16 lamps) / 2 High (32 lamps)",
),
// MONKEY (p0 skyDither): the FFXGlow combine's deband dither (was env WOW_DITHER only).
// Default 0 = the reference look; the Graphics preset's High sets 1.
ours(
@@ -2976,7 +2982,7 @@ mod tests {
assert_eq!(d["gxVSync"] != 0.0, VideoConfig::default().vsync);
// ── MONKEY (lighting): the weld for the dynamic light + shadow system's whole row set ──
//
// **All 32, as one census, with the count asserted.** Every one of them lands on
// **All 34, as one census, with the count asserted.** Every one of them lands on
// `VideoConfig` and is read from there per frame (`shadow_core::update_shadows`,
// `dynamic_interior::bridge`, `torch_shadow`), so a registered default that drifts from
// the struct's literal is a setting that reads one way in `config.toml` and renders
@@ -2986,10 +2992,12 @@ mod tests {
let shadows = VideoConfig::default();
let flag = |b: bool| if b { 1.0 } else { 0.0 };
// MONKEY (volumetric fog): include the atmospheric tier in this fixed-size default table.
let lighting: [(&str, f32); 37] = [
let lighting: [(&str, f32); 38] = [
("waterQuality", shadows.water_quality as f32),
// MONKEY (volumetric fog): weld registry and renderer defaults.
("volumetricFog", shadows.volumetric_fog as f32),
// MONKEY (lampfog): opt-in point-light fog.
("lampFog", shadows.lamp_fog as f32),
// MONKEY (post): weld registry and renderer defaults.
("bloom", shadows.bloom as f32),
("sunShafts", flag(shadows.sun_shafts)),
@@ -3048,11 +3056,11 @@ mod tests {
assert_eq!(d[name], want, "{name}: registered default left the knob");
}
// …and the census is the VideoConfig-backed row set. A name here that nothing registers
// would weld against a row the client does not have; the length is the other half — 37
// VideoConfig rows, 37 welds. MONKEY (wind): `foliageWind` is a world resource bridge.
// would weld against a row the client does not have; the length is the other half — 38
// VideoConfig rows, 38 welds (MONKEY lampfog: + lampFog). MONKEY (wind): `foliageWind` is a world resource bridge.
let welded: std::collections::BTreeSet<&str> = lighting.iter().map(|(n, _)| *n).collect();
// MONKEY (volumetric fog): the atmospheric tier joins the default-consumer weld.
assert_eq!(welded.len(), 37, "the lighting lane welds 37 distinct rows");
assert_eq!(welded.len(), 38, "the lighting lane welds 38 distinct rows");
for name in &welded {
assert!(
REGISTERED.iter().any(|r| r.name == *name),
@@ -2091,6 +2091,67 @@ fn ambient_occlusion_dropdown_is_localised_and_live() {
}
}
// MONKEY (lampfog): the end-of-page quality row uses the same numeric tier contract as the pass.
#[test]
fn lamp_fog_dropdown_is_localised_and_live() {
let xml = include_str!("../../assets/ui/OptionsFrame.xml");
let strings = &xml[xml.find("BENILLA_ADVGFX_STRINGS = {").unwrap()
..xml.find("OPTIONS_PAGE_ROWS = {").unwrap()];
let row = xml.split("<Frame name=\"$parentRowLampFog\"").nth(1).unwrap();
let on_load = row.split("<OnLoad>").nth(1).unwrap().split("</OnLoad>").next().unwrap();
for (locale, title, labels) in [
("enUS", "Lamp Fog", ["Off", "Low", "High"]),
(
"ruRU",
"Свечение фонарей в тумане",
["Выкл", "Низкое", "Высокое"],
),
] {
let mut s = audio_harness();
s.run(&format!("function GetLocale() return '{locale}' end"))
.unwrap();
s.run(strings).unwrap();
s.run(
r#"
self = {}
function OptionsRow_OnLoad(row, cvar, title, tip)
row.cvar, row.title, row.tip = cvar, title, tip
end
function OptionsDropdownRow_Setup(row, choices) row.choices = choices end
"#,
)
.unwrap();
s.run(on_load).unwrap();
assert_eq!(s.eval::<String>("return self.title").unwrap(), title);
assert!(s
.eval::<bool>(
"return getglobal(self.tip) == BENILLA_ADVGFX.tips.LAMP_FOG and string.len(getglobal(self.tip)) > 80",
)
.unwrap());
// `lampFog` registers at Off, so seed away from the first choice before asserting that
// every selection produces the expected live write (the CVar layer suppresses no-ops).
s.run("SetCVar(self.cvar, '2')").unwrap();
let _ = s.take_cvar_changes();
for (tier, label) in labels.iter().enumerate() {
assert_eq!(
s.eval::<String>(&format!("return self.choices[{}].text", tier + 1))
.unwrap(),
*label
);
let _ = s.take_cvar_changes();
s.run(&format!(
"SetCVar(self.cvar, self.choices[{}].value)",
tier + 1
))
.unwrap();
assert_eq!(
s.take_cvar_changes(),
vec![("lampFog".to_string(), tier.to_string())]
);
}
}
}
#[test]
fn water_quality_writes_numeric_tiers_with_localised_labels() {
benilla_formats::wow_data_or_skip!();
@@ -2103,8 +2164,8 @@ fn water_quality_writes_numeric_tiers_with_localised_labels() {
let mut s = harness_on(s);
s.run("ShowUIPanel(BenillaOptionsFrame) BenillaOptionsFrameCategoryListRowAdvancedGraphics:Click()").unwrap();
// MONKEY (volumetric fog): account for the atmosphere row after water.
// MONKEY (integration): all programme rows (sky, post, dither, fog, wet, wind, ao) counted.
assert_eq!(s.eval::<usize>("return table.getn(OPTIONS_PAGE_ROWS.AdvancedGraphics)").unwrap(), 28);
// MONKEY (integration): all programme rows (sky, post, dither, fog, wet, wind, ao, lamp fog) counted.
assert_eq!(s.eval::<usize>("return table.getn(OPTIONS_PAGE_ROWS.AdvancedGraphics)").unwrap(), 29);
assert_eq!(s.eval::<String>("return OPTIONS_PAGE_ROWS.AdvancedGraphics[2]").unwrap(), "RowWaterQuality");
assert_eq!(s.eval::<String>("return OPTIONS_PAGE_ROWS.AdvancedGraphics[21]").unwrap(), "RowLavaGlow");
assert_eq!(s.eval::<String>(&format!("return {ADVGFX}RowWaterQualityDropdownText:GetText()")).unwrap(), labels[1]);
@@ -3041,6 +3102,8 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
("BENILLA_TOOLTIP_SKY_QUALITY", "AdvancedGraphicsRowSkyQuality"),
// MONKEY (ao): the contact-shadow row's translated tooltip.
("BENILLA_TOOLTIP_AMBIENT_OCCLUSION", "AdvancedGraphicsRowAmbientOcclusion"),
// MONKEY (lampfog): the quality row owns a translated tooltip too.
("BENILLA_TOOLTIP_LAMP_FOG", "AdvancedGraphicsRowLampFog"),
("BENILLA_TOOLTIP_WATER_QUALITY", "AdvancedGraphicsRowWaterQuality"),
("BENILLA_TOOLTIP_LAVA_GLOW", "AdvancedGraphicsRowLavaGlow"),
("BENILLA_TOOLTIP_RENDER_SCALE", "GraphicsRowRenderScale"),
+9 -2
View File
@@ -232,6 +232,8 @@ pub(crate) struct VideoConfig {
pub(crate) sky_quality: u8,
// MONKEY (ao): screen-space contact shadows: 0 Off, 1 Low, 2 High.
pub(crate) ambient_occlusion: u8,
// MONKEY (lampfog): point-light fog tier: 0 Off, 1 nearest 16, 2 nearest 32.
pub(crate) lamp_fog: u8,
/// Brightness of lava lighting its surroundings, 0..4; 0 disables the glow.
/// Published to `benilla_world::lighting::LavaLightGain` by `dynamic_interior::bridge`.
pub(crate) lava_light_gain: f32,
@@ -497,6 +499,8 @@ impl Default for VideoConfig {
sky_quality: 0,
// MONKEY (ao): opt-in; the future Graphics preset sets High = 2.
ambient_occlusion: 0,
// MONKEY (lampfog): opt-in; zero is exactly the pre-lane render.
lamp_fog: 0,
lava_light_gain: 1.0,
fire_flicker: 1.0,
display: if windowed_env() {
@@ -557,7 +561,8 @@ pub(crate) fn on_cvar(
// Display mode (1627) — the reference's own polarity: `1` is WINDOWED (the row is
// "Windowed Mode"). `apply_window_mode` pushes it to the window when this moves.
"gxwindow" => cfg.display = display_from_flag(v),
// ── MONKEY (lighting): the dynamic light + shadow system's 33 rows ────────────────────
// ── MONKEY (lighting): the dynamic light + shadow system's 34 rows ────────────────────
// MONKEY (lampfog): lampFog is one of these live VideoConfig rows too.
// They live in THIS observer, and not in one of their own beside `shadow_core` /
// `dynamic_interior`, because of the law the arm above states: *each arm writes only its
// own resource*. Every one of these knobs IS a field of [`VideoConfig`] — the lanes read
@@ -573,7 +578,7 @@ pub(crate) fn on_cvar(
//
// Clamps are each row's own, stated beside it, exactly as for the reference rows above;
// the `ours(...)` entries in `cvars::REGISTERED` carry the matching defaults, and
// MONKEY (volumetric fog): the atmospheric tier brings the defaults weld to 33 pairs.
// MONKEY (lampfog): the two atmospheric tiers bring the defaults weld to 34 pairs.
"waterquality" => cfg.water_quality = v.clamp(0.0, 2.0) as u8,
// MONKEY (volumetric fog): constrain UI/console writes to supported tiers.
"volumetricfog" => cfg.volumetric_fog = v.clamp(0.0, 2.0) as u8,
@@ -586,6 +591,8 @@ pub(crate) fn on_cvar(
"skyquality" => cfg.sky_quality = v.clamp(0.0, 2.0) as u8,
// MONKEY (ao): constrain UI/console writes to supported tiers.
"ambientocclusion" => cfg.ambient_occlusion = v.clamp(0.0, 2.0) as u8,
// MONKEY (lampfog): 0 Off / 1 nearest 16 / 2 nearest 32.
"lampfog" => cfg.lamp_fog = v.clamp(0.0, 2.0) as u8,
"lavalightgain" => cfg.lava_light_gain = v.clamp(0.0, 4.0),
"worldshadows" => cfg.world_shadows = ev.flag(),
"charactershadows" => cfg.character_shadows = ev.flag(),
+325 -63
View File
@@ -8,25 +8,27 @@ use crate::{
world_shadow::WorldLane,
};
use benilla_world::{
lighting::{WorldTime, WowLighting},
lighting::{ResolvedPointLight, ResolvedPointLights, WorldTime, WowLighting},
static_gx::StaticGx,
view::WorldCamera,
weather::WeatherState,
wmo_portal::CameraInteriorClaim,
};
use bevy::{
camera::primitives::{Frustum, Sphere as CullSphere},
core_pipeline::{
core_3d::graph::{Core3d, Node3d},
FullscreenShader,
core_3d::graph::{Core3d, Node3d},
},
ecs::query::QueryItem,
light::VolumetricLight,
pbr::{
GpuLights, LightMeta, ViewLightsUniformOffset, ViewShadowBindings, MAX_CASCADES_PER_LIGHT,
MAX_DIRECTIONAL_LIGHTS,
GpuLights, LightMeta, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
ViewLightsUniformOffset, ViewShadowBindings,
},
prelude::*,
render::{
Render, RenderApp, RenderStartup, RenderSystems,
diagnostic::RecordDiagnostics,
extract_component::{ExtractComponent, ExtractComponentPlugin},
render_graph::{
@@ -35,14 +37,38 @@ use bevy::{
render_resource::{binding_types::*, *},
renderer::{RenderContext, RenderDevice, RenderQueue},
view::{Msaa, ViewDepthTexture, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms},
Render, RenderApp, RenderStartup, RenderSystems,
},
shader::ShaderDefVal,
};
pub(crate) struct VolumetricFogPlugin;
#[derive(Resource)]
struct FogOverride(Option<u8>, f32);
struct FogOverride {
fog: Option<u8>,
lamp: Option<u8>,
shaft_gain: f32,
}
const MAX_FOG_LAMPS: usize = 32;
const LOW_FOG_LAMPS: usize = 16;
const FOG_LAMP_RADIUS: f32 = 100.0;
#[derive(Clone, Copy)]
struct FogLamps {
positions: [Vec4; MAX_FOG_LAMPS],
colors: [Vec4; MAX_FOG_LAMPS],
count: usize,
}
impl Default for FogLamps {
fn default() -> Self {
Self {
positions: [Vec4::ZERO; MAX_FOG_LAMPS],
colors: [Vec4::ZERO; MAX_FOG_LAMPS],
count: 0,
}
}
}
#[derive(Component, Clone, Copy, ExtractComponent, ShaderType)]
struct FogView {
@@ -57,6 +83,11 @@ struct FogView {
mf_fog1: Vec4,
mf_fog2: Vec4,
mf_fog3: Vec4,
// MONKEY (lampfog): count + strength, followed by 32 resolved point-table rows. Colour already
// includes intensity, flame flicker and (for synthesised fire) fireLightGain.
lamp_meta: Vec4,
lamp_positions: [Vec4; MAX_FOG_LAMPS],
lamp_colors: [Vec4; MAX_FOG_LAMPS],
}
impl Plugin for VolumetricFogPlugin {
@@ -65,6 +96,11 @@ impl Plugin for VolumetricFogPlugin {
.ok()
.and_then(|v| v.parse::<u8>().ok())
.filter(|v| *v <= 2);
// MONKEY (lampfog): session-only capture override; the persistent setting is `lampFog`.
let lamp = std::env::var("WOW_LAMPFOG")
.ok()
.and_then(|v| v.parse::<u8>().ok())
.filter(|v| *v <= 2);
// Dev capture instrument: isolate shafts without changing surface shadows or haze.
let shaft_gain = if cfg!(feature = "dev") && std::env::var_os("WOW_CAPTURE").is_some() {
std::env::var("WOW_VOLFOG_SHAFT_GAIN")
@@ -76,10 +112,14 @@ impl Plugin for VolumetricFogPlugin {
} else {
2.0
};
app.insert_resource(FogOverride(value, shaft_gain))
.add_plugins(ExtractComponentPlugin::<FogView>::default())
.add_systems(Last, refresh_streamed_shadows.before(ShadowSet::Lanes))
.add_systems(Last, update_fog.after(ShadowSet::Lanes));
app.insert_resource(FogOverride {
fog: value,
lamp,
shaft_gain,
})
.add_plugins(ExtractComponentPlugin::<FogView>::default())
.add_systems(Last, refresh_streamed_shadows.before(ShadowSet::Lanes))
.add_systems(Last, update_fog.after(ShadowSet::Lanes));
// Keep headless policy tests independent of the renderer.
if !app.is_plugin_added::<AssetPlugin>() {
return;
@@ -117,7 +157,7 @@ fn refresh_streamed_shadows(
mut lane: ResMut<WorldLane>,
mut seen: Local<Option<u64>>,
) {
if override_value.0.unwrap_or(video.volumetric_fog) == 0 || !video.world_shadows {
if override_value.fog.unwrap_or(video.volumetric_fog) == 0 || !video.world_shadows {
*seen = None;
return;
}
@@ -135,6 +175,71 @@ fn density(minute: f32, weather: f32, indoors: bool) -> f32 {
0.004 * (1.0 + dawn * 1.5 + weather.clamp(0.0, 1.0) * 2.0) * if indoors { 0.18 } else { 1.0 }
}
// MONKEY (lampfog): independent near-field haze floor. Full daylight is exactly zero; weather
// only strengthens an already-night-time halo, and interiors keep a restrained local version.
fn lamp_haze_strength(sun_height: f32, weather: f32, indoors: bool) -> f32 {
let daylight = ((sun_height + 0.02) / 0.15).clamp(0.0, 1.0);
(1.0 - daylight) * (0.8 + 1.2 * weather.clamp(0.0, 1.0)) * if indoors { 0.55 } else { 1.0 }
}
// MONKEY (lampfog): pick from the exact CPU mirror of the packed point table. A sphere test keeps
// off-screen fixtures whose halo can still cross the view. Both exterior and interior rows are
// eligible: a street camera can see facade/room fixtures, and scene depth—not a camera-lane guess—
// cuts the view segment. This pass is deliberately unshadowed, so the remaining leakage is the
// documented tradeoff until point-light cube shadowing exists.
fn select_fog_lamps(
points: &[ResolvedPointLight],
camera_position: Vec3,
frustum: Option<&Frustum>,
limit: usize,
) -> FogLamps {
let mut candidates: Vec<(f32, ResolvedPointLight)> = points
.iter()
.copied()
.filter_map(|point| {
let point_is_indoors = point.lane > 0.5;
if point.color.max_element() <= 0.0001 {
return None;
}
let distance_squared = point.position.distance_squared(camera_position);
if distance_squared > FOG_LAMP_RADIUS * FOG_LAMP_RADIUS {
return None;
}
let reach = if point_is_indoors {
point.lane
} else {
point.range
}
.clamp(1.0, 48.0);
let visible = frustum.is_none_or(|frustum| {
frustum.intersects_sphere(
&CullSphere {
center: point.position.into(),
radius: reach,
},
false,
)
});
visible.then_some((distance_squared, point))
})
.collect();
candidates.sort_by(|left, right| left.0.total_cmp(&right.0));
let mut selected = FogLamps::default();
for (_, point) in candidates.into_iter().take(limit.min(MAX_FOG_LAMPS)) {
let reach = if point.lane > 0.5 {
point.lane
} else {
point.range
}
.clamp(1.0, 48.0);
selected.positions[selected.count] = point.position.extend(reach);
selected.colors[selected.count] = point.color.extend(0.0);
selected.count += 1;
}
selected
}
fn update_fog(
mut commands: Commands,
video: Res<VideoConfig>,
@@ -143,23 +248,36 @@ fn update_fog(
clock: Res<WorldTime>,
weather: Res<WeatherState>,
interior: Res<CameraInteriorClaim>,
// MONKEY (lampfog): the resolved point-table view published by global_light's packer.
point_lights: Res<ResolvedPointLights>,
// MONKEY (fog): the Modern fog rows (Option: absent in unit tests).
monkey: Option<Res<benilla_world::lighting::MonkeyFrame>>,
mut cameras: Query<(Entity, &Camera, &mut Camera3d), With<WorldCamera>>,
mut cameras: Query<
(
Entity,
&Camera,
&mut Camera3d,
Option<&GlobalTransform>,
Option<&Frustum>,
),
With<WorldCamera>,
>,
suns: Query<(Entity, &DirectionalLight), With<ShadowSun>>,
) {
let tier = override_value.0.unwrap_or(video.volumetric_fog).min(2);
let enabled = tier != 0 && cameras.iter().any(|(_, c, _)| c.is_active);
let tier = override_value.fog.unwrap_or(video.volumetric_fog).min(2);
let lamp_tier = override_value.lamp.unwrap_or(video.lamp_fog).min(2);
let enabled = (tier != 0 || lamp_tier != 0)
&& cameras.iter().any(|(_, camera, _, _, _)| camera.is_active);
for (entity, sun) in &suns {
// Only a tag for selecting this light in our shader. No Bevy VolumetricFog
// camera/volume is created, and the rig's lux and surface lighting stay intact.
if enabled && sun.shadows_enabled {
if tier != 0 && enabled && sun.shadows_enabled {
commands.entity(entity).insert(VolumetricLight);
} else {
commands.entity(entity).remove::<VolumetricLight>();
}
}
for (entity, camera, mut camera3d) in &mut cameras {
for (entity, camera, mut camera3d, camera_transform, frustum) in &mut cameras {
if !enabled || !camera.is_active {
commands.entity(entity).remove::<FogView>();
continue;
@@ -167,6 +285,31 @@ fn update_fog(
camera3d.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
let sun = lighting.celestial_dir();
let daylight = ((sun.y + 0.02) / 0.15).clamp(0.0, 1.0);
let weather_amount = weather
.sky_density
.max(weather.effect_density)
.clamp(0.0, 1.0);
// Exact zero by day is both the intended look and the A/B invariant. At night a constant
// floor makes nearby halos survive the zone haze's ten-yard dead zone; rain/fog raises it.
let lamp_strength = if lamp_tier == 0 {
0.0
} else {
lamp_haze_strength(sun.y, weather_amount, interior.0.is_some())
};
let lamps = if lamp_strength > 0.0 {
select_fog_lamps(
point_lights.as_slice(),
camera_transform.map_or(Vec3::ZERO, GlobalTransform::translation),
frustum,
if lamp_tier == 1 {
LOW_FOG_LAMPS
} else {
MAX_FOG_LAMPS
},
)
} else {
FogLamps::default()
};
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));
@@ -174,16 +317,26 @@ fn update_fog(
mf_fog1: Vec4::from_array(rows[1]),
mf_fog2: Vec4::from_array(rows[2]),
mf_fog3: Vec4::from_array(rows[3]),
lamp_meta: Vec4::new(lamps.count as f32, lamp_strength, 0.0, 0.0),
lamp_positions: lamps.positions,
lamp_colors: lamps.colors,
color_density: fog.extend(
density(
clock.minute_f,
weather.sky_density.max(weather.effect_density),
interior.0.is_some(),
) * if tier == 2 { 1.6 } else { 1.0 },
density(clock.minute_f, weather_amount, interior.0.is_some())
* if tier == 0 {
0.0
} else if tier == 2 {
1.6
} else {
1.0
},
),
sun_strength: Vec3::from_array(lighting.diffuse)
.lerp(Vec3::ONE, 0.5)
.extend(override_value.1 * daylight),
.extend(if tier == 0 {
0.0
} else {
override_value.shaft_gain * daylight
}),
direction_steps: sun.extend(if tier == 1 { 24.0 } else { 64.0 }),
});
}
@@ -398,6 +551,10 @@ struct Fog {
color_density: vec4<f32>, sun_strength: vec4<f32>, direction_steps: vec4<f32>,
// MONKEY (fog): MonkeyFrame fog1..fog3.
mf_fog1: vec4<f32>, mf_fog2: vec4<f32>, mf_fog3: vec4<f32>,
// MONKEY (lampfog): x=count, y=night/weather strength; packed point-table rows follow.
lamp_meta: vec4<f32>,
lamp_positions: array<vec4<f32>, 32>,
lamp_colors: array<vec4<f32>, 32>,
}
@group(0) @binding(6) var<uniform> fog: Fog;
@@ -425,6 +582,56 @@ fn visibility(light_index: u32, p: vec3<f32>) -> f32 {
// Unknown outside caster coverage is not evidence of direct sunlight.
return 0.0;
}
// MONKEY (lampfog): a mild Henyey-Greenstein lobe. The inverse-square term is integrated exactly;
// evaluating this slow angular term at the closest admitted point keeps the closed form intact.
fn lamp_phase(cosine: f32) -> f32 {
let anisotropy = 0.2;
let denom = 1.0 + anisotropy * anisotropy - 2.0 * anisotropy * cosine;
return 0.07957747 * (1.0 - anisotropy * anisotropy) / (denom * sqrt(denom));
}
// MONKEY (lampfog): analytic line integral of a softened inverse-square source over the visible
// camera-to-depth segment. It is intentionally UNSHADOWED; scene depth cuts the segment, but no
// point-light cube map is sampled. Range is a smooth screen-space gate around the exact sphere
// intersection. The exponential soft cap plus destination headroom keeps halos below 1.0 so a
// later bloom pass cannot turn a stacked row of lamps into white slabs.
fn lamp_halos(ray: vec3<f32>, distance: f32) -> vec3<f32> {
var energy = vec3<f32>(0.0);
let lamp_count = u32(fog.lamp_meta.x);
for (var lamp_index = 0u; lamp_index < 32u; lamp_index += 1u) {
if (lamp_index >= lamp_count) { break; }
let position_range = fog.lamp_positions[lamp_index];
let to_lamp = position_range.xyz - view.world_position;
let projection = dot(to_lamp, ray);
let perpendicular = to_lamp - ray * projection;
let perpendicular_sq = dot(perpendicular, perpendicular);
let range_sq = position_range.w * position_range.w;
if (perpendicular_sq >= range_sq) { continue; }
let half_span = sqrt(max(range_sq - perpendicular_sq, 0.0));
let segment_start = max(0.0, projection - half_span);
let segment_end = min(distance, projection + half_span);
if (segment_end <= segment_start) { continue; }
// Integral dt / (core^2 + perpendicular^2 + (t - projection)^2).
let core_radius = 1.25;
let height = sqrt(perpendicular_sq + core_radius * core_radius);
let integral = (atan((segment_end - projection) / height)
- atan((segment_start - projection) / height)) / height;
let closest = clamp(projection, segment_start, segment_end);
let sample_to_lamp = view.world_position + ray * closest - position_range.xyz;
let light_path = sample_to_lamp / max(length(sample_to_lamp), 0.0001);
let phase = lamp_phase(dot(light_path, -ray));
let edge = 1.0 - smoothstep(position_range.w * 0.72, position_range.w,
sqrt(perpendicular_sq));
energy += max(fog.lamp_colors[lamp_index].rgb, vec3<f32>(0.0))
* (integral * phase * edge);
}
let capped = min(energy * fog.lamp_meta.y * 0.9, vec3<f32>(0.75));
return vec3<f32>(1.0) - exp(-capped);
}
@fragment
fn fragment(@builtin(position) pixel: vec4<f32>) -> @location(0) vec4<f32> {
let xy = vec2<i32>(pixel.xy);
@@ -434,45 +641,52 @@ fn fragment(@builtin(position) pixel: vec4<f32>) -> @location(0) vec4<f32> {
let q = view.world_from_clip * vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), max(z, 0.000001), 1.0);
let delta = q.xyz / q.w - view.world_position;
let distance = min(length(delta), 150.0);
if (distance <= 10.0) { return source; }
if (distance <= 10.0 && fog.lamp_meta.x < 0.5) { return source; }
let ray = normalize(delta);
let optical_depth = fog.color_density.w * fog_path(distance);
var opacity = 1.0 - exp(-optical_depth);
var haze_rgb = fog.color_density.rgb;
if (fog_hook::fog_is_modern(fog.mf_fog3)) {
// MONKEY (fog): the sky (depth 0, clamped to 150 yd above) already carries its own
// horizon fog, so it takes no distance haze; the haze colour is the shared fog colour.
if (z <= 0.0) { opacity = 0.0; }
haze_rgb = fog_hook::fog_modern_colour(fog.color_density.rgb, ray, length(delta),
fog.mf_fog1, fog.mf_fog2, fog.mf_fog3);
}
let luma = vec3(0.2126, 0.7152, 0.0722);
var haze = mix(source.rgb, haze_rgb, opacity);
// A darker zone colour may change hue, never the pixel's luminance. Add
// neutral headroom proportionally so saturated channels cannot clip dark.
let missing = max(0.0, dot(source.rgb - haze, luma));
let headroom = max(vec3(0.0), vec3(1.0) - haze);
haze += headroom * clamp(missing / max(dot(headroom, luma), 0.00001), 0.0, 1.0);
if (fog.sun_strength.w > 0.0) {
let g = 0.65;
let cosine = dot(ray, fog.direction_steps.xyz);
let denom = 1.0 + g*g - 2.0*g*cosine;
let phase = 0.07957747 * (1.0-g*g) / (denom * sqrt(denom));
let count = u32(fog.direction_steps.w);
let step_size = (distance - 10.0) / f32(count);
var scatter = 0.0;
for (var light_index = 0u; light_index < lights.n_directional_lights; light_index += 1u) {
if ((lights.directional_lights[light_index].flags & 3u) != 3u) { continue; }
for (var step = 0u; step < count; step += 1u) {
// Fixed midpoint samples: no temporal noise in this gamma lane.
let t = 10.0 + (f32(step) + 0.5) * step_size;
let density = fog.color_density.w * smoothstep(10.0, 30.0, t);
scatter += visibility(light_index, view.world_position + ray*t)
* exp(-fog.color_density.w * fog_path(t)) * density * step_size;
}
var haze = source.rgb;
if (distance > 10.0) {
let optical_depth = fog.color_density.w * fog_path(distance);
var opacity = 1.0 - exp(-optical_depth);
var haze_rgb = fog.color_density.rgb;
if (fog_hook::fog_is_modern(fog.mf_fog3)) {
// MONKEY (fog): the sky (depth 0, clamped to 150 yd above) already carries its own
// horizon fog, so it takes no distance haze; the haze colour is the shared fog colour.
if (z <= 0.0) { opacity = 0.0; }
haze_rgb = fog_hook::fog_modern_colour(fog.color_density.rgb, ray, length(delta),
fog.mf_fog1, fog.mf_fog2, fog.mf_fog3);
}
let shafts = fog.sun_strength.rgb * fog.sun_strength.w * phase * scatter;
haze += max(vec3(0.0), vec3(1.0) - haze) * (vec3(1.0) - exp(-shafts));
haze = mix(source.rgb, haze_rgb, opacity);
// A darker zone colour may change hue, never the pixel's luminance. Add
// neutral headroom proportionally so saturated channels cannot clip dark.
let missing = max(0.0, dot(source.rgb - haze, luma));
let headroom = max(vec3(0.0), vec3(1.0) - haze);
haze += headroom * clamp(missing / max(dot(headroom, luma), 0.00001), 0.0, 1.0);
if (fog.sun_strength.w > 0.0) {
let g = 0.65;
let cosine = dot(ray, fog.direction_steps.xyz);
let denom = 1.0 + g*g - 2.0*g*cosine;
let phase = 0.07957747 * (1.0-g*g) / (denom * sqrt(denom));
let count = u32(fog.direction_steps.w);
let step_size = (distance - 10.0) / f32(count);
var scatter = 0.0;
for (var light_index = 0u; light_index < lights.n_directional_lights; light_index += 1u) {
if ((lights.directional_lights[light_index].flags & 3u) != 3u) { continue; }
for (var step = 0u; step < count; step += 1u) {
// Fixed midpoint samples: no temporal noise in this gamma lane.
let t = 10.0 + (f32(step) + 0.5) * step_size;
let density = fog.color_density.w * smoothstep(10.0, 30.0, t);
scatter += visibility(light_index, view.world_position + ray*t)
* exp(-fog.color_density.w * fog_path(t)) * density * step_size;
}
}
let shafts = fog.sun_strength.rgb * fog.sun_strength.w * phase * scatter;
haze += max(vec3(0.0), vec3(1.0) - haze) * (vec3(1.0) - exp(-shafts));
}
}
if (fog.lamp_meta.x > 0.5) {
let halo = lamp_halos(ray, distance);
haze += max(vec3(0.0), vec3(1.0) - haze) * halo;
}
return vec4(haze, source.a);
}
@@ -487,12 +701,16 @@ mod tests {
app.init_resource::<VideoConfig>()
.init_resource::<StaticGx>()
.init_resource::<WorldLane>()
.insert_resource(FogOverride(Some(0), 2.0))
.insert_resource(FogOverride {
fog: Some(0),
lamp: None,
shaft_gain: 2.0,
})
.add_systems(Update, refresh_streamed_shadows);
app.world_mut().clear_trackers();
app.world_mut().run_schedule(Update);
assert!(!app.world().resource_ref::<WorldLane>().is_changed());
app.world_mut().resource_mut::<FogOverride>().0 = Some(1);
app.world_mut().resource_mut::<FogOverride>().fog = Some(1);
app.world_mut().run_schedule(Update);
assert!(app.world().resource_ref::<WorldLane>().is_changed());
app.world_mut().clear_trackers();
@@ -509,6 +727,45 @@ mod tests {
assert!(density(390.0, 1.0, true) < noon);
}
#[test]
fn lamp_haze_is_exactly_off_by_day_and_weather_only_strengthens_night() {
assert_eq!(lamp_haze_strength(1.0, 1.0, false), 0.0);
let clear = lamp_haze_strength(-1.0, 0.0, false);
assert!(lamp_haze_strength(-1.0, 1.0, false) > clear * 2.0);
assert!(lamp_haze_strength(-1.0, 0.0, true) < clear);
}
#[test]
fn lamp_picker_keeps_the_nearest_across_both_lanes_and_tier_limit() {
let mut points: Vec<_> = (1..=40)
.rev()
.map(|distance| ResolvedPointLight {
position: Vec3::X * distance as f32,
range: 48.0,
color: Vec3::new(0.8, 0.4, 0.1),
lane: 0.0,
})
.collect();
// Interior and exterior table rows compete by distance; black rows never consume a slot.
points.push(ResolvedPointLight {
position: Vec3::X * 0.25,
range: 48.0,
color: Vec3::ONE,
lane: 8.0,
});
points.push(ResolvedPointLight {
position: Vec3::X * 0.5,
range: 48.0,
color: Vec3::ZERO,
lane: 0.0,
});
let low = select_fog_lamps(&points, Vec3::ZERO, None, LOW_FOG_LAMPS);
assert_eq!(low.count, LOW_FOG_LAMPS);
assert_eq!(low.positions[0], Vec4::new(0.25, 0.0, 0.0, 8.0));
assert_eq!(low.positions[1].x, 1.0);
assert_eq!(low.positions[LOW_FOG_LAMPS - 1].x, 15.0);
assert_eq!(low.colors[0].truncate(), Vec3::ONE);
assert_eq!(low.colors[1].truncate(), Vec3::new(0.8, 0.4, 0.1));
}
#[test]
fn live_tiers_shadow_loss_inactive_camera_and_off() {
let mut app = App::new();
app.init_resource::<VideoConfig>()
@@ -516,7 +773,12 @@ mod tests {
.init_resource::<WorldTime>()
.init_resource::<WeatherState>()
.init_resource::<CameraInteriorClaim>()
.insert_resource(FogOverride(None, 2.0))
.init_resource::<ResolvedPointLights>()
.insert_resource(FogOverride {
fog: None,
lamp: None,
shaft_gain: 2.0,
})
.add_systems(Update, update_fog);
let camera = app
.world_mut()
@@ -563,10 +825,10 @@ mod tests {
app.update();
assert!(app.world().get::<FogView>(camera).is_none());
app.world_mut().get_mut::<Camera>(camera).unwrap().is_active = true;
app.world_mut().resource_mut::<FogOverride>().0 = Some(0);
app.world_mut().resource_mut::<FogOverride>().fog = Some(0);
app.update();
assert!(app.world().get::<FogView>(camera).is_none());
app.world_mut().resource_mut::<FogOverride>().0 = None;
app.world_mut().resource_mut::<FogOverride>().fog = None;
app.update();
assert!(app.world().get::<FogView>(camera).is_some());
}
@@ -614,6 +614,29 @@ pub struct WorldPointLight {
pub range: f32,
}
/// MONKEY (lampfog): one live entry exactly as [`build_light_data`] resolved it for the shared
/// point table. Colour is already `colour x intensity`, with the source's live gain and flame
/// flicker folded in; position is Bevy world space and `lane` keeps the table's exterior/interior
/// discriminator (an interior lane's value is also its effective reach).
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct ResolvedPointLight {
pub position: Vec3,
pub range: f32,
pub color: Vec3,
pub lane: f32,
}
/// MONKEY (lampfog): read-only CPU view of the shared point table for post-process consumers.
/// The GPU blob stays private and its pinned layout does not grow; readers get only the live rows.
#[derive(Resource, Default)]
pub struct ResolvedPointLights(Vec<ResolvedPointLight>);
impl ResolvedPointLights {
pub fn as_slice(&self) -> &[ResolvedPointLight] {
&self.0
}
}
/// The packed light for this frame, extracted for [`upload_light`].
#[derive(Resource, Clone, Copy, ExtractResource)]
struct WowLightData(LightStd430);
@@ -636,6 +659,8 @@ pub struct SharedLightBuffer(pub Buffer);
/// Registers the light pack, the probe publish, their extracts and the render-world uploads.
pub(super) fn register(app: &mut App) {
app.init_resource::<WowLightData>()
// MONKEY (lampfog): CPU mirror of only the live point rows; not extracted or GPU-bound.
.init_resource::<ResolvedPointLights>()
// MONKEY (room gate): the per-fixture room claims, packed beside the point table.
.init_resource::<RoomClaimTable>()
.init_resource::<WorldShadowActive>()
@@ -1308,6 +1333,8 @@ fn build_light_data(
// The per-frame portal PVS, for the room term below ([`LightRooms`]).
portals: Query<&crate::wmo_portal::WmoPortalInstance>,
mut data: ResMut<WowLightData>,
// MONKEY (lampfog): publish the already-resolved live rows without exposing the GPU blob.
mut resolved_points: ResMut<ResolvedPointLights>,
// MONKEY (room gate): packed in the same walk as the point table, so the two can never
// disagree about which entry is which.
mut claims: ResMut<RoomClaimTable>,
@@ -1595,6 +1622,20 @@ fn build_light_data(
fresh.points[2 * i + 1] = [rgb[0], rgb[1], rgb[2], *lane];
claim.write(&mut claims.0[i * ROOM_CLAIM_STRIDE..][..ROOM_CLAIM_STRIDE]);
}
// MONKEY (lampfog): same order and values as the rows above. This is intentionally updated
// after the 255-entry truncate, so a CPU post effect can never see a light the shaders cannot.
let next_points: Vec<_> = pts
.iter()
.map(|(_, p, range, rgb, _, lane, _, _)| ResolvedPointLight {
position: *p,
range: *range,
color: Vec3::from_array(*rgb),
lane: *lane,
})
.collect();
if resolved_points.0 != next_points {
resolved_points.0 = next_points;
}
// Entries past the count are stale in the point table by design (the count row guards every
// reader) — but the claim table is read at the SAME index, so a stale head there would gate a
// live light with a dead building's identity. Clear the tail instead of trusting the count.
@@ -2061,6 +2102,7 @@ mod tests {
.init_resource::<crate::dev_state::DebugState>()
.init_resource::<crate::view::ViewDistance>()
.init_resource::<WowLightData>()
.init_resource::<ResolvedPointLights>()
// MONKEY (room gate): the packer writes the claim table in the same walk.
.init_resource::<RoomClaimTable>()
.init_resource::<Time>()
@@ -2293,6 +2335,12 @@ mod tests {
(synthetic[0] - 1.0).abs() < 1e-4,
"the synthesised light takes the gain: {synthetic:?}"
);
// MONKEY (lampfog): the CPU accessor is the live table, not a second light calculation.
let resolved = app.world().resource::<ResolvedPointLights>();
assert_eq!(resolved.as_slice().len(), 2);
assert_eq!(resolved.as_slice()[0].position, Vec3::X);
assert!((resolved.as_slice()[0].color.x - authored[0]).abs() < 1e-4);
assert!((resolved.as_slice()[1].color.x - synthetic[0]).abs() < 1e-4);
// Zero is the kill switch: the invented light commits black, the authored one is unmoved.
app.world_mut().insert_resource(FireLightGain(0.0));
@@ -2300,6 +2348,11 @@ mod tests {
let data = app.world().resource::<WowLightData>().0;
assert_eq!(data.points[3], [0.0, 0.0, 0.0, 0.0], "gain 0 = lane off");
assert!((data.points[1][0] - 2.0).abs() < 1e-4, "authored unaffected");
assert_eq!(
app.world().resource::<ResolvedPointLights>().as_slice()[1].color,
Vec3::ZERO,
"the accessor carries the same live fire-gain fold"
);
}
/// GOLDEN — MONKEY (spellLightGain): a SPELL light takes `spellLightGain` **instead of**
+1 -1
View File
@@ -42,7 +42,7 @@ pub use flicker::{flame_kind_for, flicker_seed, FlameFlicker, FlameKind, Flicker
pub use global_light::{
interior_reach, m2_light_reach, new_shared_light_buffer, DynamicInteriors, FireLightGain,
ClaimFade, LightLane, LightLitRooms, LightReach, LightRooms, RoomClaimTable,
SharedLightBuffer,
ResolvedPointLight, ResolvedPointLights, SharedLightBuffer,
ShadowDistance, ShadowFilterGaussian, ShadowProxyLight, SyntheticFireLight,
WorldShadowActive,
};
+48 -28
View File
@@ -101,17 +101,17 @@
## Modules (top-level, per crate)
- **benilla-adt** (lib.rs): combined_alpha
- **benilla-app** (lib.rs): addon_harness area area_poi area_trigger asset_churn aura_visual bindings blob_shadow bowstring camera_shake capture char_create char_select chat_bubble chr_classes cinematic combat_log combat_text console crash creature_anim cursor cvars death debug_panel dev doodad_events entities fishing_line footprints game_plugins game_tip glue glue_strings go_anim go_templates hover_log items loading_screen local_state login minimap name_persist nameplates names net npc_text opaque2d pending_item_ops perf pipe_warm player poi_marker portrait preflight probe_shield query_cache quest_markers raid_marks ranged_flex realm_select realmlist run_mode screen_fade screenshot shaders shutdown smart_rect sound spell swing_refusal target test_support text_filter text_reshape textinput transport tutorial ui_action ui_auction ui_aura ui_bank ui_battlefield ui_battlefield_positions ui_battlefield_score ui_bind_confirm ui_binder ui_cast ui_char ui_chat ui_craft ui_dialog_verbs ui_dressup ui_duel ui_follow ui_gamma ui_gm_ticket ui_gossip ui_guild ui_hide ui_honor ui_inspect ui_instance ui_item_text ui_items ui_layout ui_logout ui_loot ui_loot_roll ui_macro ui_mail ui_merchant ui_mirror ui_models ui_net ui_party ui_pass ui_pet ui_pet_book ui_pet_doll ui_pet_stats ui_petition ui_quest ui_quest_log ui_quest_share ui_reputation ui_saved ui_script ui_session ui_shapeshift ui_social ui_spellbook ui_stable ui_summon ui_tabard ui_talent ui_talent_wipe ui_taxi ui_text ui_tooltip ui_trade ui_tradeskill ui_trainer ui_unit ui_world_map video vplates weapon_trail world_backdrop world_state world_state_ui
- **benilla-assets** (lib.rs): adt anim_rng blp column_grid coords gpu_blp m2 materials minimap_grid model spatial_cache terrain tex_filter trace wdt wmo world_assets
- **benilla-formats** (lib.rs): anim_data area_poi area_sound area_table area_trigger auction_house bank_bag_slot_prices camera_shakes chain characters chat_channels chr_classes cinematics creature_families creature_sound creature_types creatures dbc death_thud durability elevators emit_timing emote_text emotes environmental_damage exhaustion factions footsteps game_tips gameobjects garble gm_ticket_category ground_effects install item_random_properties item_sounds item_visuals itembagfamily itemclass items itemsets itemsubclass languages light liquid loading_screen lock lock_type macro_icons maps material minimap_translate models npc_greeting page_text_material particles pet_stats quest_headers quest_info race_pvp_team race_sound ribbons server_messages sheathe skill_lines sound_entries sound_provider sound_water spell_focus spell_mechanic spell_visual spells stable_slot_prices stationery talents taxi taxi_nodes taxi_path terrain text_filter_lists tga transport_period transports unit_blood value_track vocal_ui_sounds wdl weapon_impact weapon_swing wmo_area world_map_area world_map_continent world_map_overlay world_state_ui zone_map
- **benilla-m2** (lib.rs): error model skin tests track
- **benilla-mpq** (lib.rs): crypto
- **benilla-protocol** (lib.rs): auth events guid messages wire world
- **benilla-srp** (lib.rs): vanilla_header
- **benilla-ui** (lib.rs): bindings_xml civil framexml justify layout loader markup messages order script source strings toc widget
- **benilla-visual** (lib.rs): relight
- **benilla-world** (lib.rs): art_scope assets bgwin billboard boot build_id clouds clutter collision decal dev_state doodad_anim entity_shade exterior_cull ffx_glow final_pass frame_pace ground_fx instance_tint interact interior lighting liquid log_ring mac_quit map_proj mat_anim_table mesh_tag model_fade model_forms model_render modkeys particles ribbons ride_frame rig_anim rig_palette rig_rider schedule shaders sky sky_order skybox static_gx static_merge straddle sun surface terrain_stream thread_qos view vis_chain water_fx wdl weather wmo_portal world_census world_map world_plugins world_point world_unit worldview zfill
- **benilla-adt** (lib.rs): combined_alpha
- **benilla-app** (lib.rs): addon_harness area area_poi area_trigger asset_churn aura_visual bindings blob_shadow bowstring camera_shake capture char_create char_select character_shadow chat_bubble chr_classes cinematic combat_log combat_text console crash creature_anim cursor cvars death debug_panel dev doodad_events dynamic_interior entities fishing_line footprints game_plugins game_tip glue glue_strings go_anim go_templates hover_log items loading_screen local_state login minimap monkey_gfx name_persist nameplates names net npc_text opaque2d pending_item_ops perf pipe_warm player poi_marker portrait preflight probe_shield query_cache quest_markers raid_marks ranged_flex realm_select realmlist run_mode screen_fade screenshot shaders shadow_core shutdown smart_rect sound spell swing_refusal target test_support text_filter text_reshape textinput torch_shadow transport tutorial ui_action ui_auction ui_aura ui_bank ui_battlefield ui_battlefield_positions ui_battlefield_score ui_bind_confirm ui_binder ui_cast ui_char ui_chat ui_craft ui_dialog_verbs ui_dressup ui_duel ui_follow ui_gamma ui_gm_ticket ui_gossip ui_guild ui_hide ui_honor ui_inspect ui_instance ui_item_text ui_items ui_layout ui_logout ui_loot ui_loot_roll ui_macro ui_mail ui_merchant ui_mirror ui_models ui_net ui_party ui_pass ui_pet ui_pet_book ui_pet_doll ui_pet_stats ui_petition ui_quest ui_quest_log ui_quest_share ui_reputation ui_saved ui_script ui_session ui_shapeshift ui_social ui_spellbook ui_stable ui_summon ui_tabard ui_talent ui_talent_wipe ui_taxi ui_text ui_tooltip ui_trade ui_tradeskill ui_trainer ui_unit ui_world_map video volumetric_fog vplates weapon_trail world_backdrop world_shadow world_state world_state_ui
- **benilla-assets** (lib.rs): adt anim_rng blp column_grid coords gpu_blp m2 materials minimap_grid model spatial_cache terrain tex_filter trace water_depth wdt wmo world_assets
- **benilla-formats** (lib.rs): anim_data area_poi area_sound area_table area_trigger auction_house bank_bag_slot_prices camera_shakes chain characters chat_channels chr_classes cinematics creature_families creature_sound creature_types creatures dbc death_thud durability elevators emit_timing emote_text emotes environmental_damage exhaustion factions fire_light footsteps game_tips gameobjects garble gm_ticket_category ground_effects install item_random_properties item_sounds item_visuals itembagfamily itemclass items itemsets itemsubclass languages light liquid loading_screen lock lock_type macro_icons maps material minimap_translate models npc_greeting page_text_material particles pet_stats quest_headers quest_info race_pvp_team race_sound ribbons room_claim server_messages sheathe skill_lines sound_entries sound_provider sound_water spell_focus spell_mechanic spell_visual spells stable_slot_prices stationery talents taxi taxi_nodes taxi_path terrain text_filter_lists tga transport_period transports unit_blood value_track vocal_ui_sounds wdl weapon_impact weapon_swing wmo_area world_map_area world_map_continent world_map_overlay world_state_ui zone_map
- **benilla-m2** (lib.rs): error model skin tests track
- **benilla-mpq** (lib.rs): crypto
- **benilla-protocol** (lib.rs): auth events guid messages wire world
- **benilla-srp** (lib.rs): vanilla_header
- **benilla-ui** (lib.rs): bindings_xml civil framexml justify layout loader markup messages order script source strings toc widget
- **benilla-visual** (lib.rs): relight
- **benilla-world** (lib.rs): art_scope assets bgwin billboard boot build_id clouds clutter collision decal dev_state doodad_anim entity_shade exterior_cull ffx_glow final_pass frame_pace ground_fx instance_tint interact interior lighting liquid log_ring mac_quit map_proj mat_anim_table mesh_tag model_fade model_forms model_render modkeys particles ribbons ride_frame rig_anim rig_palette rig_rider schedule shaders sky sky_order skybox static_gx static_merge straddle sun surface terrain_stream thread_qos view vis_chain water_fx wdl weather wmo_portal world_census world_map world_plugins world_point world_unit worldview zfill
## CLI binaries
@@ -123,22 +123,29 @@
## WGSL shaders (`crates/*/src/shaders/`, embedded)
- `embedded://benilla_app/shaders/shadow_caster.wgsl`
- `embedded://benilla_app/shaders/shadow_caster_cutout_prepass.wgsl`
- `embedded://benilla_app/shaders/ui_add.wgsl`
- `embedded://benilla_app/shaders/ui_gamma.wgsl`
- `embedded://benilla_app/shaders/ui_node_gamma.wgsl`
- `embedded://benilla_app/shaders/ui_quad.wgsl`
- `embedded://benilla_app/shaders/ui_slice_gamma.wgsl`
- `embedded://benilla_assets/shaders/enhanced_water.wgsl`
- `embedded://benilla_assets/shaders/fog_hook.wgsl`
- `embedded://benilla_assets/shaders/liquid.wgsl`
- `embedded://benilla_assets/shaders/monkey_frame.wgsl`
- `embedded://benilla_assets/shaders/shadow_hook.wgsl`
- `embedded://benilla_assets/shaders/terrain.wgsl`
- `embedded://benilla_assets/shaders/wdl.wgsl`
- `embedded://benilla_assets/shaders/wow_model.wgsl`
- `embedded://benilla_world/shaders/celestial.wgsl`
- `embedded://benilla_world/shaders/cloud.wgsl`
- `embedded://benilla_world/shaders/ffx_glow.wgsl`
- `embedded://benilla_world/shaders/sky_vertex.wgsl`
- `embedded://benilla_world/shaders/sky.wgsl`
- `embedded://benilla_world/shaders/sky_vertex.wgsl`
- `embedded://benilla_world/shaders/star.wgsl`
- `embedded://benilla_world/shaders/static_gx.wgsl`
- `embedded://benilla_world/shaders/torch_depth.wgsl`
- `embedded://benilla_world/shaders/wow_effect.wgsl`
## Instruments — `$WOW_*` switches (env-var read sites)
@@ -152,6 +159,7 @@
- `WOW_ADDON_PROBE_TRACE` — benilla-protocol/examples/addon_chat_probe.rs
- `WOW_ALLOW_ACCOUNT` — benilla-app/src/login/mod.rs, benilla-app/src/run_mode.rs
- `WOW_AMBIGUITY_DUMP` — benilla-app/src/game_plugins.rs
- `WOW_AMBIGUITY_DUMP_POST` — benilla-app/src/game_plugins.rs
- `WOW_ANIM_COST` — benilla-app/src/creature_anim/driver.rs
- `WOW_ANIM_PARK_ALL` — benilla-app/src/creature_anim/lod.rs
- `WOW_ANISO` — benilla-app/src/cvars.rs, benilla-assets/src/tex_filter.rs
@@ -167,6 +175,7 @@
- `WOW_AUDIT_WMO` — benilla-world/src/wmo_portal/audit/mod.rs
- `WOW_BARE_DOODAD_COLLIDERS` — benilla-world/src/terrain_stream/collider.rs
- `WOW_BG` — benilla-world/src/bgwin.rs
- `WOW_BLEED` — benilla-world/src/lighting/daylight.rs
- `WOW_BLOB_VIS` — benilla-world/src/terrain_stream/merge.rs
- `WOW_BLOB_VIS_UID` — benilla-world/src/terrain_stream/merge.rs
- `WOW_BOOTH_DUMP` — benilla-app/src/portrait/test_bake.rs
@@ -174,13 +183,14 @@
- `WOW_BUBBLE_TRACE` — benilla-app/src/chat_bubble.rs
- `WOW_CAM_CHANGED` — benilla-app/src/perf/mod.rs
- `WOW_CAM_DUMP` — benilla-app/src/player/camera.rs
- `WOW_CAPTURE` — benilla-app/src/capture/mod.rs, benilla-app/src/capture/scenarios.rs, benilla-app/src/char_select/addons.rs, benilla-app/src/cvars.rs, benilla-app/src/lib.rs, benilla-app/src/local_state.rs, benilla-app/src/login/mod.rs, benilla-app/src/name_persist.rs, benilla-app/src/net.rs, benilla-app/src/player/camera_saved.rs, benilla-app/src/run_mode.rs, benilla-app/src/sound/mod.rs, benilla-app/src/ui_chat/settings.rs, benilla-app/src/ui_chat/tests.rs, benilla-app/src/ui_macro/tests.rs, benilla-app/src/ui_saved.rs, benilla-app/src/ui_script/addons.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/ui_script/world_entry_tests.rs, benilla-app/src/video.rs, benilla-world/src/bgwin.rs, benilla-world/src/clouds/mod.rs, benilla-world/src/dev_state.rs
- `WOW_CAPTURE` — benilla-app/src/capture/mod.rs, benilla-app/src/capture/scenarios.rs, benilla-app/src/char_select/addons.rs, benilla-app/src/cvars.rs, benilla-app/src/lib.rs, benilla-app/src/local_state.rs, benilla-app/src/login/mod.rs, benilla-app/src/name_persist.rs, benilla-app/src/net.rs, benilla-app/src/player/camera_saved.rs, benilla-app/src/run_mode.rs, benilla-app/src/sound/mod.rs, benilla-app/src/ui_chat/settings.rs, benilla-app/src/ui_chat/tests.rs, benilla-app/src/ui_macro/tests.rs, benilla-app/src/ui_saved.rs, benilla-app/src/ui_script/addons.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/ui_script/world_entry_tests.rs, benilla-app/src/video.rs, benilla-app/src/volumetric_fog.rs, benilla-world/src/bgwin.rs, benilla-world/src/clouds/mod.rs, benilla-world/src/dev_state.rs, benilla-world/src/liquid/surface.rs
- `WOW_CAPTURE_AGE` — benilla-app/src/capture/mod.rs
- `WOW_CAPTURE_DEADLINE` — benilla-app/src/capture/mod.rs
- `WOW_CAPTURE_OUT` — benilla-app/src/capture/mod.rs
- `WOW_CAPTURE_REHOVER` — benilla-app/src/ui_script/mod.rs
- `WOW_CAPTURE_STABLE` — benilla-app/src/capture/mod.rs
- `WOW_CAPTURE_UI` — benilla-app/src/capture/scenarios.rs, benilla-app/src/ui_script/mod.rs, benilla-app/src/video.rs
- `WOW_CAPTURE_WATER_T` — benilla-world/src/liquid/surface.rs, benilla-world/src/liquid/waves.rs
- `WOW_CAST_TRACE` — benilla-app/src/net.rs
- `WOW_CHAR` — benilla-app/src/capture/probe_mail.rs, benilla-app/src/char_select/mod.rs, benilla-app/src/cvars.rs, benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/net/io.rs, benilla-app/src/realm_select/smoke.rs, benilla-app/src/run_mode.rs
- `WOW_CHARCREATE_DIALS` — benilla-app/src/char_create/mod.rs
@@ -209,10 +219,12 @@
- `WOW_CRASH_INJECT` — benilla-app/src/perf/crash_inject.rs
- `WOW_CREATE_SPLINE` — benilla-app/src/net/motion/spline.rs
- `WOW_CREATE_TEST` — benilla-app/src/portrait/glue_booth.rs, benilla-world/src/bgwin.rs
- `WOW_CULL_TRACE` — benilla-world/src/exterior_cull.rs
- `WOW_CULLDUMP` — benilla-world/src/wmo_portal/mod.rs
- `WOW_CULL_TRACE` — benilla-world/src/exterior_cull.rs
- `WOW_CURSOR_TRACE` — benilla-app/src/cursor.rs
- `WOW_DATA` — benilla-app/src/local_state.rs, benilla-formats/src/install.rs, benilla-formats/tests/wow_data_env.rs, benilla-formats/tests/wow_data_none.rs
- `WOW_DAYLIGHT` — benilla-world/src/lighting/daylight.rs
- `WOW_DAYLIGHT_SEAM_EPS` — benilla-world/src/lighting/daylight.rs
- `WOW_DEMO_BOTTOM_BARS` — benilla-app/src/ui_script/mod.rs
- `WOW_DEPTH` — benilla-app/src/capture/depth_probe.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_DEPTH_AFTER` — benilla-app/src/capture/depth_probe.rs
@@ -234,6 +246,7 @@
- `WOW_FEED_GATE_CHECK` — benilla-app/src/ui_script/gate.rs, benilla-world/src/bgwin.rs
- `WOW_FEED_GATE_TRACE` — benilla-app/src/ui_script/gate.rs, benilla-world/src/bgwin.rs
- `WOW_FIXED_DT` — benilla-world/src/frame_pace.rs
- `WOW_FOGMODEL` — benilla-world/src/lighting/fog_model.rs
- `WOW_FOG_DUMP` — benilla-world/src/lighting/resolve.rs, benilla-world/src/liquid/query.rs
- `WOW_FOLLOW_TRACE` — benilla-app/src/player/follow.rs
- `WOW_FORCE_SUB` — benilla-world/src/liquid/mod.rs
@@ -284,6 +297,7 @@
- `WOW_IO_BUFFER` — benilla-app/src/sound/mixer.rs
- `WOW_JITTER` — benilla-app/src/capture/probes/jitter.rs, benilla-app/src/dev.rs
- `WOW_JITTER_RAW` — benilla-app/src/capture/probes/jitter.rs
- `WOW_LAMPFOG` — benilla-app/src/volumetric_fog.rs
- `WOW_LAYOUT_DERIVE_TRACE` — benilla-ui/src/script/model.rs
- `WOW_LAYOUT_PROF` — benilla-ui/src/script/layout.rs
- `WOW_LAYOUT_TOUCH_TRACE` — benilla-ui/src/script/model.rs
@@ -310,10 +324,10 @@
- `WOW_MATANIM_DUTY` — benilla-world/src/doodad_anim/mat_anim.rs
- `WOW_MATANIM_LIVE` — benilla-world/src/doodad_anim/mat_anim.rs
- `WOW_MATANIM_PROBE` — benilla-world/src/doodad_anim/mat_anim.rs
- `WOW_MERGE_CENSUS` — benilla-world/src/static_merge.rs
- `WOW_MERGE_FADERS` — benilla-world/src/terrain_stream/merge.rs
- `WOW_MERGED_FADE` — benilla-assets/src/materials.rs
- `WOW_MERGED_SLOT` — benilla-assets/src/materials.rs
- `WOW_MERGE_CENSUS` — benilla-world/src/static_merge.rs
- `WOW_MERGE_FADERS` — benilla-world/src/terrain_stream/merge.rs
- `WOW_MESH_EVENTS` — benilla-app/src/perf/mod.rs
- `WOW_MESH_HOLDERS` — benilla-app/src/perf/mod.rs
- `WOW_MESH_TOUCH` — benilla-app/src/perf/mod.rs
@@ -333,6 +347,10 @@
- `WOW_NAME_DIST` — benilla-app/src/capture/mod.rs
- `WOW_NAME_EL` — benilla-app/src/capture/mod.rs
- `WOW_NAME_H` — benilla-app/src/capture/mod.rs
- `WOW_NODE_PROBE` — benilla-app/src/capture/probes/world_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_NOPORTALCULL` — benilla-world/src/dev_state.rs
- `WOW_NOSOUND` — benilla-app/src/sound/mod.rs
- `WOW_NOVSYNC` — benilla-app/src/video.rs, benilla-world/src/worldview.rs
- `WOW_NO_ALPHATEST` — benilla-world/src/model_render.rs
- `WOW_NO_ANIM_LOD` — benilla-app/src/creature_anim/lod.rs, benilla-app/src/lib.rs
- `WOW_NO_BC` — benilla-assets/src/gpu_blp.rs
@@ -352,18 +370,14 @@
- `WOW_NO_TILE_CELLS` — benilla-world/src/terrain_stream/furnish.rs
- `WOW_NO_TILE_DROP` — benilla-world/src/terrain_stream.rs
- `WOW_NO_TWIST` — benilla-app/src/creature_anim/twist.rs
- `WOW_NODE_PROBE` — benilla-app/src/capture/probes/world_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_NOPORTALCULL` — benilla-world/src/dev_state.rs
- `WOW_NOSOUND` — benilla-app/src/sound/mod.rs
- `WOW_NOVSYNC` — benilla-app/src/video.rs, benilla-world/src/worldview.rs
- `WOW_OCEAN_DEPTH_DIV` — benilla-formats/src/liquid.rs
- `WOW_PANEL` — benilla-world/src/dev_state.rs
- `WOW_PART_CHURN` — benilla-app/src/perf/mod.rs
- `WOW_PARTICLE_CENSUS` — benilla-app/src/capture/probes/particle_census.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_PARTICLE_DEPTHDUMP` — benilla-world/src/particles/depthdump.rs
- `WOW_PARTICLE_DEPTHDUMP_BONES` — benilla-world/src/particles/depthdump.rs
- `WOW_PARTICLE_FLAT` — benilla-world/src/particles/render.rs
- `WOW_PARTICLE_NODEPTH` — benilla-world/src/particles/render.rs
- `WOW_PART_CHURN` — benilla-app/src/perf/mod.rs
- `WOW_PASS` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs
- `WOW_PERF_HUD` — benilla-app/src/perf/hud.rs
- `WOW_PHASE` — benilla-app/src/capture/phase_probe.rs, benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
@@ -387,15 +401,15 @@
- `WOW_PIN_UID` — benilla-world/src/wmo_portal/audit/pin.rs
- `WOW_PIN_WMO` — benilla-world/src/wmo_portal/audit/pin.rs
- `WOW_PIPE_TRACE` — benilla-app/src/pipe_warm/mod.rs
- `WOW_POINTS_DUMP` — benilla-world/src/lighting/global_light.rs
- `WOW_POINTS_DUMP` — benilla-world/src/lighting/daylight.rs, benilla-world/src/lighting/global_light.rs, benilla-world/src/lighting/lava_light.rs
- `WOW_PORTRAIT_TEST` — benilla-app/src/portrait/mod.rs, benilla-app/src/portrait/test_bake.rs, benilla-world/src/bgwin.rs
- `WOW_PORTRAIT_TEST_SKIN` — benilla-app/src/portrait/test_bake.rs
- `WOW_PROBE` — benilla-app/src/dev.rs, benilla-app/src/lib.rs, benilla-world/src/bgwin.rs
- `WOW_PROBE_AUCTION` — benilla-app/src/dev.rs
- `WOW_PROBE_BANK` — benilla-app/src/dev.rs
- `WOW_PROBE_BG` — benilla-app/src/capture/probe_bg.rs, benilla-app/src/dev.rs
- `WOW_PROBE_BG_SAMPLES` — benilla-app/src/capture/probe_bg.rs
- `WOW_PROBE_BGQUEUE` — benilla-app/src/dev.rs
- `WOW_PROBE_BG_SAMPLES` — benilla-app/src/capture/probe_bg.rs
- `WOW_PROBE_BINDER` — benilla-app/src/dev.rs
- `WOW_PROBE_BOOK` — benilla-app/src/dev.rs
- `WOW_PROBE_CAM` — benilla-app/src/capture/probe_cam.rs
@@ -445,8 +459,8 @@
- `WOW_REMOTE_IDLE_GATE` — benilla-app/src/net/motion/remote.rs
- `WOW_REMOTE_SNAP` — benilla-app/src/net/motion/remote.rs
- `WOW_RENDER_SCALE` — benilla-app/src/cvars.rs, benilla-app/src/world_backdrop.rs
- `WOW_RES_CENSUS` — benilla-app/src/perf/census.rs
- `WOW_RESIZE` — benilla-app/src/capture/mod.rs
- `WOW_RES_CENSUS` — benilla-app/src/perf/census.rs
- `WOW_REVEAL` — benilla-app/src/capture/probes/reveal.rs, benilla-app/src/dev.rs
- `WOW_RIG` — benilla-app/src/capture/probe_rig.rs, benilla-app/src/dev.rs, benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/probe_shield.rs, benilla-app/src/realm_select/smoke.rs, benilla-app/src/run_mode.rs, benilla-world/src/bgwin.rs
- `WOW_RIG_CENSUS` — benilla-world/src/rig_palette.rs
@@ -455,10 +469,12 @@
- `WOW_ROW_BLOAT` — benilla-app/src/perf/census.rs, benilla-app/src/perf/mod.rs
- `WOW_SCHED_CENSUS` — benilla-app/src/dev.rs, benilla-world/src/bgwin.rs
- `WOW_SHADE_CENSUS` — benilla-world/src/entity_shade.rs
- `WOW_SHADOW_TRACE` — benilla-app/src/shadow_core.rs
- `WOW_SHOT_REQUIRE` — benilla-app/src/capture/live_shot.rs
- `WOW_SHOT_REQUIRE_DIST` — benilla-app/src/capture/live_shot.rs
- `WOW_SKY_DEPTH` — benilla-assets/src/materials.rs
- `WOW_SOUND_PROBE` — benilla-app/src/sound/probe.rs
- `WOW_SPELL_LIGHT` — benilla-app/src/entities/spell_fx/lifecycle.rs
- `WOW_STALL` — benilla-app/src/capture/probes/stall.rs, benilla-app/src/dev.rs
- `WOW_STALL_INJECT` — benilla-app/src/perf/stall.rs
- `WOW_STALL_SAMPLE` — benilla-app/src/perf/stall.rs
@@ -468,8 +484,8 @@
- `WOW_STATIC_GX_WMO` — benilla-world/src/static_gx/mod.rs
- `WOW_STATIC_MERGE` — benilla-world/src/terrain_stream/merge.rs
- `WOW_STREAM_TRACE` — benilla-app/src/perf/mod.rs
- `WOW_TAB_TRACE` — benilla-app/src/target/scan.rs
- `WOW_TABHOVER` — benilla-app/src/capture/fixtures.rs
- `WOW_TAB_TRACE` — benilla-app/src/target/scan.rs
- `WOW_TEARDOWN_INJECT` — benilla-app/src/perf/stall.rs
- `WOW_TEXT_PROBE` — benilla-app/src/ui_script/extract/text.rs
- `WOW_THREADS` — benilla-world/src/boot.rs
@@ -477,9 +493,11 @@
- `WOW_TILE_TRACE` — benilla-app/src/ui_models/mod.rs
- `WOW_TILE_UNLOAD` — benilla-world/src/assets/mod.rs
- `WOW_TINT_PROBE` — benilla-app/src/aura_visual.rs
- `WOW_TORCH_TRACE` — benilla-app/src/torch_shadow.rs, benilla-world/src/static_gx/torch_depth.rs
- `WOW_TRAIL_CENSUS` — benilla-app/src/capture/probes/trail_census.rs, benilla-app/src/dev.rs
- `WOW_TRILINEAR` — benilla-app/src/cvars.rs, benilla-assets/src/tex_filter.rs
- `WOW_TWIST_GAP` — benilla-app/src/player/body_pose.rs
- `WOW_UISCRIPT_DUMP` — benilla-app/tests/ui_script_wall.rs
- `WOW_UI_COST` — benilla-app/src/ui_script/extract/mod.rs
- `WOW_UI_DIFF` — benilla-app/src/ui_pass.rs, benilla-app/src/ui_script/extract/mod.rs
- `WOW_UI_GATE` — benilla-app/src/ui_script/extract/mod.rs
@@ -488,20 +506,22 @@
- `WOW_UI_PROBE` — benilla-app/src/ui_pass.rs
- `WOW_UI_SCALE` — benilla-app/src/cvars.rs, benilla-app/src/ui_script/mod.rs
- `WOW_UI_SPLICE_VERIFY` — benilla-app/src/ui_script/extract/mod.rs
- `WOW_UISCRIPT_DUMP` — benilla-app/tests/ui_script_wall.rs
- `WOW_UNATTENDED` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs, benilla-world/src/bgwin.rs
- `WOW_UNIT_VISUALS` — benilla-app/src/capture/probes/visual_census.rs, benilla-app/src/dev.rs
- `WOW_UNIT_VISUALS_RADIUS` — benilla-app/src/capture/probes/visual_census.rs
- `WOW_UPLOAD_BUDGET` — benilla-world/src/world_plugins.rs
- `WOW_USER` — benilla-app/src/login/mod.rs, benilla-app/src/net.rs, benilla-app/src/run_mode.rs
- `WOW_VIS_DUMP` — benilla-app/src/capture/probes/live_fps.rs
- `WOW_VIS_TRACE` — benilla-world/src/model_render/visibility.rs
- `WOW_VISTA_AT` — benilla-app/src/capture/mod.rs
- `WOW_VISTA_FACE` — benilla-app/src/capture/mod.rs
- `WOW_VISTA_MIN` — benilla-app/src/capture/mod.rs
- `WOW_VISTA_PITCH` — benilla-app/src/capture/mod.rs
- `WOW_VIS_DUMP` — benilla-app/src/capture/probes/live_fps.rs
- `WOW_VIS_TRACE` — benilla-world/src/model_render/visibility.rs
- `WOW_VOLFOG` — benilla-app/src/volumetric_fog.rs
- `WOW_VOLFOG_SHAFT_GAIN` — benilla-app/src/volumetric_fog.rs
- `WOW_VPLATE_TRACE` — benilla-app/src/vplates.rs
- `WOW_WARM_SLICE` — benilla-app/src/pipe_warm/mod.rs
- `WOW_WATER` — benilla-world/src/liquid/scene_depth.rs
- `WOW_WATER_CLIP` — benilla-assets/src/materials.rs
- `WOW_WAVE_DUMP` — benilla-world/src/ffx_glow.rs
- `WOW_WEATHER` — benilla-world/src/weather/mod.rs