diff --git a/LIGHTING.md b/LIGHTING.md
index 2122d3df..9a5644ba 100644
--- a/LIGHTING.md
+++ b/LIGHTING.md
@@ -24,6 +24,7 @@ behind each constant.
| Moon shadows | at night the same shadow rig re-aims at the moon and casts a faint shadow; dims only the night sky term, never point lights | `moonShadowStrength` |
| Ground-effect spells | Flamestrike, Rain of Fire, Consecration, Flare and fire traps light the ground for their duration; frost and nature areas stay dark | `spellLightGain` |
| Volumetric fog | near-field haze that converges on the zone fog colour (clear within 10 yd, full by 150 yd; mistier at dawn and in bad weather, faint indoors) and sun/moon light shafts through gaps, sampled from the shadow map; own fullscreen pass after the main pass (`benilla-app/src/volumetric_fog.rs`) | Advanced Graphics → Volumetric Fog (Off/Low/High), cvar `volumetricFog`, env `WOW_VOLFOG=0\|1\|2` |
+| Zone skyboxes | the painted sky a zone's `LightParams` names (clear-weather slot, storm slot lerped by the storm blend) for the living, fading in by the Light sphere falloff with the modern client's crossfade (ghost > WMO > zone); extended `LightSkybox.dbc` flags `0x1` day cycle, `0x2` keep sun/moon/stars/clouds, `0x4` fog-colour horizon cone, plus a celestial second model; every skybox now plays its bones, texture transforms, colour and alpha tracks (`benilla-world/src/skybox.rs`, `skybox_anim.rs`, `benilla-formats/src/light/skybox.rs`) | Advanced Graphics → Zone Skyboxes, cvar `zoneSkyboxes` (0 Off default, 1 On; High = 1), env `WOW_ZONE_SKYBOXES=0\|1` |
| 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 /
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 71e19e82..59bd286d 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -495,6 +495,8 @@
-- MONKEY (volumetric fog): localised atmospheric quality row.
volumetricFog = "Volumetric Fog",
fogOff = "Off",
+ -- MONKEY (skybox): the zone skybox row.
+ zoneSkyboxes = "Zone Skyboxes",
waterQuality = "Water Quality",
waterClassic = "Classic",
waterEnhanced = "Enhanced",
@@ -534,6 +536,12 @@
-- MONKEY (volumetric fog): explain cost and the shadow-map dependency.
VOLUMETRIC_FOG =
"Off disables volumetric fog. Low adds nearby haze and sun or moon shafts with fewer steps; High uses more steps for smoother shafts. Mist is heavier at dawn and in bad weather, and subtle indoors. Shafts require shadows; with shadows off only haze remains.",
+ -- MONKEY (skybox): what the zone skybox row changes.
+ ZONE_SKYBOXES =
+ "Draws the painted sky a zone's lighting names, as later expansions do: "
+ .. "the volcanic sky of the Burning Steppes, the alien sky of the Blasted Lands "
+ .. "and the starry night over Mount Hyjal, fading in at the zone's edge. "
+ .. "Off keeps the original client's sky everywhere but ghost and dungeon skies. Nearly free.",
WATER_QUALITY =
"Classic is the original client's water. Enhanced adds soft shorelines, "
.. "animated foam around shores and objects, moving wave highlights, sky reflection "
@@ -619,6 +627,8 @@
-- MONKEY (volumetric fog): Russian atmospheric quality row.
volumetricFog = "Объёмный туман",
fogOff = "Выкл",
+ -- MONKEY (skybox): the zone skybox row.
+ zoneSkyboxes = "Небосводы зон",
waterQuality = "Качество воды",
waterClassic = "Классическое",
waterEnhanced = "Улучшенное",
@@ -660,6 +670,12 @@
-- MONKEY (volumetric fog): localised effect and cost explanation.
VOLUMETRIC_FOG =
"Выкл отключает объёмный туман. Низкое добавляет дымку вблизи и лучи солнца или луны с меньшим числом шагов; Высокое использует больше шагов для более плавных лучей. Туман гуще на рассвете и в непогоду, а в помещениях слабее. Для лучей нужны тени; без теней остаётся только дымка.",
+ -- MONKEY (skybox): what the zone skybox row changes.
+ ZONE_SKYBOXES =
+ "Рисует нарисованное небо, заданное освещением зоны, как в поздних дополнениях: "
+ .. "вулканическое небо Пылающих степей, чужое небо Выжженных земель "
+ .. "и звёздную ночь над горой Хиджал, с плавным появлением на границе зоны. "
+ .. "«Выкл.» оставляет небо оригинального клиента везде, кроме неба призрака и подземелий. Почти бесплатно.",
WATER_QUALITY =
"«Классическое» — вода оригинального клиента. «Улучшенное» добавляет плавные переходы у берегов, "
.. "анимированную пену у берегов и вокруг объектов, подвижные блики на волнах, отражение неба "
@@ -2602,11 +2618,23 @@
})
-
+
+
+
+ OptionsRow_OnLoad(self, "zoneSkyboxes",
+ BENILLA_ADVGFX.zoneSkyboxes,
+ "BENILLA_TOOLTIP_ZONE_SKYBOXES")
+
+
+
+
+
+
+
OptionsRow_OnLoad(self, "characterShadows",
BENILLA_ADVGFX.characterShadows,
diff --git a/crates/benilla-app/src/capture/scenarios.rs b/crates/benilla-app/src/capture/scenarios.rs
index d970a9fd..07631c96 100644
--- a/crates/benilla-app/src/capture/scenarios.rs
+++ b/crates/benilla-app/src/capture/scenarios.rs
@@ -1378,6 +1378,72 @@ pub(super) const ON_DEMAND: &[Scenario] = &[
look: LAVA_RIVER_LOOK,
minute: 0,
ui: None,
+ }, // MONKEY (skybox): zone skyboxes (run with `WOW_ZONE_SKYBOXES=1`) and the two stock skybox
+ // lanes. Karazahn40 (Turtle map 814) is the one stock sphere naming a clear-slot skybox
+ // (Light 553, HellfireSkyBox); the zone shots need the sky patch.
+ Scenario {
+ name: "skybox-karazhan-noon",
+ map: Some(814),
+ eye: SKYBOX_KZ_EYE,
+ look: SKYBOX_KZ_LOOK,
+ minute: 720,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-karazhan-night",
+ map: Some(814),
+ eye: SKYBOX_KZ_EYE,
+ look: SKYBOX_KZ_LOOK,
+ minute: 0,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-steppes-noon",
+ map: Some(MAP_AZEROTH),
+ eye: SKYBOX_STEPPES_EYE,
+ look: SKYBOX_STEPPES_LOOK,
+ minute: 720,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-steppes-night",
+ map: Some(MAP_AZEROTH),
+ eye: SKYBOX_STEPPES_EYE,
+ look: SKYBOX_STEPPES_LOOK,
+ minute: 0,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-blasted-noon",
+ map: Some(MAP_AZEROTH),
+ eye: SKYBOX_BLASTED_EYE,
+ look: SKYBOX_BLASTED_LOOK,
+ minute: 720,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-blasted-night",
+ map: Some(MAP_AZEROTH),
+ eye: SKYBOX_BLASTED_EYE,
+ look: SKYBOX_BLASTED_LOOK,
+ minute: 0,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-hyjal-noon",
+ map: Some(MAP_KALIMDOR),
+ eye: SKYBOX_HYJAL_EYE,
+ look: SKYBOX_HYJAL_LOOK,
+ minute: 720,
+ ui: None,
+ },
+ Scenario {
+ name: "skybox-hyjal-night",
+ map: Some(MAP_KALIMDOR),
+ eye: SKYBOX_HYJAL_EYE,
+ look: SKYBOX_HYJAL_LOOK,
+ minute: 0,
+ ui: None,
},
];
@@ -1482,6 +1548,17 @@ pub(crate) fn ui_opted_in() -> bool {
std::env::var("WOW_CAPTURE_UI").as_deref() == Ok("1") || scenario_declares_ui()
}
+// MONKEY (skybox): the skybox shots, eye just above the ground at a sphere's centre, pitched up so
+// the upper frame is sky.
+const SKYBOX_KZ_EYE: [f32; 3] = [-6474.0, -2912.0, 40.0];
+const SKYBOX_KZ_LOOK: [f32; 3] = [-6300.0, -2800.0, 95.0];
+const SKYBOX_STEPPES_EYE: [f32; 3] = [-7979.0, -2571.0, 260.0];
+const SKYBOX_STEPPES_LOOK: [f32; 3] = [-7800.0, -2450.0, 320.0];
+const SKYBOX_BLASTED_EYE: [f32; 3] = [-11300.0, -3073.0, 30.0];
+const SKYBOX_BLASTED_LOOK: [f32; 3] = [-11120.0, -2960.0, 90.0];
+const SKYBOX_HYJAL_EYE: [f32; 3] = [4637.0, -4461.0, 1060.0];
+const SKYBOX_HYJAL_LOOK: [f32; 3] = [4800.0, -4350.0, 1120.0];
+
#[cfg(test)]
mod ui_opt_in_tests {
use super::*;
diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs
index 31a7eced..90e2fafb 100644
--- a/crates/benilla-app/src/cvars.rs
+++ b/crates/benilla-app/src/cvars.rs
@@ -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 (skybox): the living player's zone skybox from `LightParams`; High sets 1.
+ 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",
+ ),
ours(
"waterQuality",
"1",
diff --git a/crates/benilla-app/src/debug_panel/world.rs b/crates/benilla-app/src/debug_panel/world.rs
index b7d26a01..c673c2d5 100644
--- a/crates/benilla-app/src/debug_panel/world.rs
+++ b/crates/benilla-app/src/debug_panel/world.rs
@@ -105,12 +105,14 @@ pub(super) fn world_section(ui: &mut egui::Ui, world: &mut WorldReadout) {
// 4-second crossfade (== the interior-fog blend, one number): `w 0.00` beside a name means
// the flood published a skybox the crossfade hasn't engaged — faithful standing outside
// the gate, not a resolve bug.
- match world.skybox.0.as_deref() {
- Some(path) => {
+ // MONKEY (skybox): the heaviest layer of the weighted list, at its own weight.
+ match world.skybox.primary() {
+ Some(layer) => {
+ let path = layer.path.as_str();
let leaf = path.rsplit('\\').next().unwrap_or(path);
line.push_str(&format!(
- " · skybox {leaf} w {:.2}",
- world.skybox_weight.0
+ " · skybox {leaf} w {:.2} (stand-down {:.2})",
+ layer.weight, world.skybox_weight.0
));
}
None => line.push_str(" · sky gradient"),
diff --git a/crates/benilla-app/src/game_plugins.rs b/crates/benilla-app/src/game_plugins.rs
index adda66ec..db88690d 100644
--- a/crates/benilla-app/src/game_plugins.rs
+++ b/crates/benilla-app/src/game_plugins.rs
@@ -245,6 +245,8 @@ impl PluginGroup for GamePlugins {
.add(crate::dynamic_interior::DynamicInteriorPlugin)
// MONKEY (volumetric fog): bridge the live setting to Bevy.
.add(crate::volumetric_fog::VolumetricFogPlugin)
+ // MONKEY (skybox): bridge the zone-skybox setting to the world lane.
+ .add(crate::zone_skybox::ZoneSkyboxPlugin)
// The realmlist (decision 1667) — the logon address the login screen edits. Same reason as
// VideoPlugin above: it is a CVar knob, so its resource has to exist before `load_config`.
.add(crate::realmlist::RealmlistPlugin)
diff --git a/crates/benilla-app/src/lib.rs b/crates/benilla-app/src/lib.rs
index a7f9ac23..862b5fc1 100644
--- a/crates/benilla-app/src/lib.rs
+++ b/crates/benilla-app/src/lib.rs
@@ -74,6 +74,8 @@ mod doodad_events;
/// MONKEY (dynamic interiors): the cvar → benilla-world bridge for the fixture-lit interior lane.
// MONKEY (volumetric fog): optional near-field atmosphere plugin.
mod volumetric_fog;
+// MONKEY (skybox): the zoneSkyboxes cvar bridge.
+mod zone_skybox;
mod dynamic_interior;
mod entities;
mod fishing_line;
diff --git a/crates/benilla-app/src/zone_skybox.rs b/crates/benilla-app/src/zone_skybox.rs
new file mode 100644
index 00000000..ce4342cc
--- /dev/null
+++ b/crates/benilla-app/src/zone_skybox.rs
@@ -0,0 +1,45 @@
+//! MONKEY (skybox): the `zoneSkyboxes` cvar's bridge to [`benilla_world::skybox::ZoneSkyboxes`].
+//! `WOW_ZONE_SKYBOXES=0|1` overrides it for the session, which is how a hermetic capture (no config
+//! file, registry defaults) turns the lane on.
+
+use bevy::prelude::*;
+
+/// The session override, read once.
+#[derive(Resource, Clone, Copy)]
+struct ZoneSkyboxOverride(Option);
+
+pub(crate) struct ZoneSkyboxPlugin;
+
+impl Plugin for ZoneSkyboxPlugin {
+ fn build(&self, app: &mut App) {
+ let forced = std::env::var("WOW_ZONE_SKYBOXES")
+ .ok()
+ .and_then(|v| v.trim().parse::().ok())
+ .map(|v| v != 0);
+ app.insert_resource(ZoneSkyboxOverride(forced))
+ .add_observer(on_cvar)
+ .add_systems(Startup, apply_override);
+ }
+}
+
+fn apply_override(
+ forced: Res,
+ zone: Option>,
+) {
+ if let (Some(on), Some(mut zone)) = (forced.0, zone) {
+ zone.set_if_neq(benilla_world::skybox::ZoneSkyboxes(on));
+ }
+}
+
+fn on_cvar(
+ ev: On,
+ forced: Res,
+ zone: Option>,
+) {
+ if !ev.is("zoneSkyboxes") || forced.0.is_some() {
+ return;
+ }
+ if let Some(mut zone) = zone {
+ zone.set_if_neq(benilla_world::skybox::ZoneSkyboxes(ev.flag()));
+ }
+}