From 36d03ee462eb04a2bd318d37c8819bb03dc29f19 Mon Sep 17 00:00:00 2001
From: pkuzic
Date: Fri, 25 Sep 2026 02:02:19 +0100
Subject: [PATCH] MONKEY (post): route emissive materials into HDR
Co-Authored-By: GPT-5.6 Sol (Codex)
---
LIGHTING.md | 2 ++
WATER.md | 1 +
crates/benilla-app/assets/ui/OptionsFrame.xml | 31 ++++++++++++++--
crates/benilla-app/src/cvars.rs | 6 ++++
crates/benilla-app/src/dynamic_interior.rs | 10 +++++-
crates/benilla-app/src/video.rs | 6 ++++
crates/benilla-assets/src/materials.rs | 2 ++
.../src/shaders/emissive_hook.wgsl | 36 +++++++++++++++++++
crates/benilla-assets/src/shaders/liquid.wgsl | 9 ++++-
.../benilla-assets/src/shaders/wow_model.wgsl | 5 +++
.../src/lighting/global_light.rs | 13 +++++++
crates/benilla-world/src/lighting/mod.rs | 2 ++
.../benilla-world/src/shaders/static_gx.wgsl | 8 +++++
.../benilla-world/src/shaders/wow_effect.wgsl | 6 +++-
14 files changed, 132 insertions(+), 5 deletions(-)
create mode 100644 crates/benilla-assets/src/shaders/emissive_hook.wgsl
diff --git a/LIGHTING.md b/LIGHTING.md
index 2122d3df..f1852454 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` |
+| HDR emission and bloom | lit WMO windows, additive models/particles and magma can exceed display white; a soft-capped fullscreen halo is added before the faithful FFX clamp | Advanced Graphics → Bloom (Off/Low/High), cvar `bloom`; Off is the unchanged reference image, suggested High preset value 2 |
| 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 /
@@ -35,6 +36,7 @@ is its own module, documented in `WATER.md`.
## Where the code lives
- **Shaders**: `crates/benilla-assets/src/shaders/shadow_hook.wgsl` (shared shadow sampling),
+ `crates/benilla-assets/src/shaders/emissive_hook.wgsl` (shared opt-in HDR multipliers),
`crates/benilla-world/src/shaders/torch_depth.wgsl`, and the lighting lanes inside
`static_gx.wgsl`, `wow_model.wgsl` and `terrain.wgsl`. The three receivers mirror each other; the
comments say where.
diff --git a/WATER.md b/WATER.md
index 9ee06550..4938656a 100644
--- a/WATER.md
+++ b/WATER.md
@@ -46,6 +46,7 @@ the technique is listed in the table below. Do not remove either.
| Beach surf | a travelling wave front, a lace of foam that dissolves behind it, a swash sheet on the sand | tier |
| Swimmers | wake foam, treading rings and a visual-only bob with the swell | tier |
| Lava glow | magma surfaces emit warm point lights through the lighting module | Advanced Graphics → Lava Glow, `lavaLightGain` |
+| Lava HDR | magma partially resists distance fog and feeds the screen-space halo | Advanced Graphics → Bloom, `bloom` (Off/Low/High) |
Rivers deliberately have NO flow effect: a derived current was tried and removed at the owner's
request. If it ever returns it should read the authored MCLQ flow records, not derive one.
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 71e19e82..bc03017f 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -494,6 +494,8 @@
lightingQuality = "Lighting Quality",
-- MONKEY (volumetric fog): localised atmospheric quality row.
volumetricFog = "Volumetric Fog",
+ -- MONKEY (post): HDR excess + halo quality.
+ bloom = "Bloom",
fogOff = "Off",
waterQuality = "Water Quality",
waterClassic = "Classic",
@@ -532,6 +534,9 @@
.. "Low, Medium and High use Enhanced water; mirror reflections stay opt-in. "
.. "Changing a preset member shows Custom when no preset matches.",
-- MONKEY (volumetric fog): explain cost and the shadow-map dependency.
+ -- MONKEY (post): Off guarantees the pre-HDR image.
+ BLOOM =
+ "Makes fire, lava, spells and lit windows carry a soft halo. Low uses a smaller blur chain; High is smoother. Off leaves the original image unchanged.",
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.",
WATER_QUALITY =
@@ -616,6 +621,8 @@
ruRU = {
page = "Расширенная графика",
lightingQuality = "Качество освещения",
+ -- MONKEY (post): Russian HDR excess + halo quality row.
+ bloom = "Свечение",
-- MONKEY (volumetric fog): Russian atmospheric quality row.
volumetricFog = "Объёмный туман",
fogOff = "Выкл",
@@ -657,6 +664,9 @@
.. "сохраняют ваши значения. «Низкое», «Среднее» и «Высокое» используют улучшенную воду; "
.. "зеркальные отражения включаются отдельно. Изменение настройки набора показывает «Своё», "
.. "если ни один набор больше не совпадает.",
+ -- MONKEY (post): localised bloom effect and Off contract.
+ BLOOM =
+ "Добавляет мягкое сияние огню, лаве, заклинаниям и освещённым окнам. Низкое качество использует короткое размытие, высокое — более плавное. Выключение сохраняет исходное изображение.",
-- MONKEY (volumetric fog): localised effect and cost explanation.
VOLUMETRIC_FOG =
"Выкл отключает объёмный туман. Низкое добавляет дымку вблизи и лучи солнца или луны с меньшим числом шагов; Высокое использует больше шагов для более плавных лучей. Туман гуще на рассвете и в непогоду, а в помещениях слабее. Для лучей нужны тени; без теней остаётся только дымка.",
@@ -795,7 +805,7 @@
-- three of eighteen shadow settings and no hint that the other fifteen existed.
AdvancedGraphics = {
-- MONKEY (volumetric fog): include in refresh, defaults and search.
- "RowLightingQuality", "RowWaterQuality", "RowVolumetricFog",
+ "RowLightingQuality", "RowWaterQuality", "RowVolumetricFog", "RowBloom",
"RowCharacterShadows", "RowWorldShadows", "RowShadowDistance",
"RowShadowResolution", "RowMoonShadows",
"RowInteriorLight", "RowInteriorShadows", "RowExteriorShadows", "RowTorchSoft",
@@ -2602,11 +2612,28 @@
})
-
+
+
+
+ OptionsRow_OnLoad(self, "bloom", BENILLA_ADVGFX.bloom,
+ "BENILLA_TOOLTIP_BLOOM")
+ OptionsDropdownRow_Setup(self, {
+ { text = BENILLA_ADVGFX.fogOff, value = "0" },
+ { text = BENILLA_ADVGFX.qLow, value = "1" },
+ { text = BENILLA_ADVGFX.qHigh, value = "2" },
+ })
+
+
+
+
+
+
+
+
OptionsRow_OnLoad(self, "characterShadows",
BENILLA_ADVGFX.characterShadows,
diff --git a/crates/benilla-app/src/cvars.rs b/crates/benilla-app/src/cvars.rs
index 31a7eced..66156e99 100644
--- a/crates/benilla-app/src/cvars.rs
+++ b/crates/benilla-app/src/cvars.rs
@@ -921,6 +921,8 @@ 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 (post): tier 0 leaves every emissive site and the frame byte-identical.
+ ours("bloom", "0", "benilla's own: HDR emissive bloom, 0 Off / 1 Low / 2 High"),
ours(
"waterQuality",
"1",
@@ -2902,6 +2904,8 @@ mod tests {
("waterQuality", shadows.water_quality as f32),
// MONKEY (volumetric fog): weld registry and renderer defaults.
("volumetricFog", shadows.volumetric_fog as f32),
+ // MONKEY (post): weld registry and renderer defaults.
+ ("bloom", shadows.bloom as f32),
("lavaLightGain", shadows.lava_light_gain),
// The two sun lanes and the cascade they share.
("worldShadows", flag(shadows.world_shadows)),
@@ -3133,6 +3137,8 @@ mod tests {
"waterQuality" => video.water_quality as f32,
// MONKEY (volumetric fog): verify presets reach the renderer.
"volumetricFog" => video.volumetric_fog as f32,
+ // MONKEY (post): the post lane's live tier.
+ "bloom" => video.bloom as f32,
"lavaLightGain" => video.lava_light_gain,
"fireLightGain" => video.fire_light_gain,
"moonShadowStrength" => video.moon_shadow_strength,
diff --git a/crates/benilla-app/src/dynamic_interior.rs b/crates/benilla-app/src/dynamic_interior.rs
index e34976f6..1fbb6fa8 100644
--- a/crates/benilla-app/src/dynamic_interior.rs
+++ b/crates/benilla-app/src/dynamic_interior.rs
@@ -25,7 +25,9 @@
//!
//! Water quality and lava glow use the same guarded bridge into their renderer resources.
use benilla_assets::WaterQuality;
-use benilla_world::lighting::{DynamicInteriors, FireLightGain, LavaLightGain, MoonShadowStrength, SpellLightGain};
+use benilla_world::lighting::{
+ DynamicInteriors, EmissiveTier, FireLightGain, LavaLightGain, MoonShadowStrength, SpellLightGain,
+};
use bevy::prelude::*;
use crate::video::VideoConfig;
@@ -60,6 +62,8 @@ fn bridge(
mut moon: ResMut,
mut water: ResMut,
mut lava: ResMut,
+ // MONKEY (post): bloom also arms the HDR source hooks through the shared light blob.
+ mut emissive: ResMut,
) {
if water.0 != video.water_quality {
water.0 = video.water_quality;
@@ -67,6 +71,9 @@ fn bridge(
if lava.0 != video.lava_light_gain {
lava.0 = video.lava_light_gain;
}
+ if emissive.0 != video.bloom {
+ emissive.0 = video.bloom;
+ }
if fire.0 != video.fire_light_gain {
fire.0 = video.fire_light_gain;
}
@@ -130,6 +137,7 @@ mod tests {
.init_resource::()
.init_resource::()
.init_resource::()
+ .init_resource::()
.add_plugins(DynamicInteriorPlugin);
assert_eq!(app.world().resource::().0, VideoConfig::default().water_quality);
assert_eq!(app.world().resource::().0, VideoConfig::default().lava_light_gain);
diff --git a/crates/benilla-app/src/video.rs b/crates/benilla-app/src/video.rs
index 07c17824..30a2b12f 100644
--- a/crates/benilla-app/src/video.rs
+++ b/crates/benilla-app/src/video.rs
@@ -222,6 +222,8 @@ pub(crate) struct VideoConfig {
pub(crate) water_quality: u8,
// MONKEY (volumetric fog): live camera raymarch tier: 0 Off, 1 Low, 2 High.
pub(crate) volumetric_fog: u8,
+ // MONKEY (post): emissive HDR + bloom quality, 0 Off / 1 Low / 2 High.
+ pub(crate) bloom: 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,
@@ -467,6 +469,8 @@ impl Default for VideoConfig {
water_quality: 1,
// MONKEY (volumetric fog): default to the inexpensive atmosphere.
volumetric_fog: 1,
+ // MONKEY (post): opt-in; the future High graphics preset selects tier 2.
+ bloom: 0,
lava_light_gain: 1.0,
fire_flicker: 1.0,
display: if windowed_env() {
@@ -547,6 +551,8 @@ pub(crate) fn on_cvar(
"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,
+ // MONKEY (post): constrain UI/console writes to the supported bloom tiers.
+ "bloom" => cfg.bloom = 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(),
diff --git a/crates/benilla-assets/src/materials.rs b/crates/benilla-assets/src/materials.rs
index 18aa1682..fec812b8 100644
--- a/crates/benilla-assets/src/materials.rs
+++ b/crates/benilla-assets/src/materials.rs
@@ -29,6 +29,8 @@ pub fn register_shaders(app: &mut App) {
// (loaded eagerly so `#import benilla::shadow_hook` resolves in terrain/model/static_gx). This is
// the ONE place the realtime directional-shadow term lives; the receivers just call into it.
load_shader_library!(app, "shaders/shadow_hook.wgsl");
+ // MONKEY (post): shared opt-in HDR multipliers; tier 0 is an exact identity.
+ load_shader_library!(app, "shaders/emissive_hook.wgsl");
// MONKEY (enhanced water): the optional water module, `benilla::enhanced_water` (WATER.md).
load_shader_library!(app, "shaders/enhanced_water.wgsl");
}
diff --git a/crates/benilla-assets/src/shaders/emissive_hook.wgsl b/crates/benilla-assets/src/shaders/emissive_hook.wgsl
new file mode 100644
index 00000000..8668b08d
--- /dev/null
+++ b/crates/benilla-assets/src/shaders/emissive_hook.wgsl
@@ -0,0 +1,36 @@
+#define_import_path benilla::emissive_hook
+
+// MONKEY (post): the opt-in HDR seam shared by windows, additive models/effects and magma.
+// `light_diffuse.w` was the reference pack's unused clamp marker (always 1); the CPU now adds
+// the `bloom` tier, so 1 remains a byte-identical Off sentinel without growing the light blob.
+const EMISSIVE_WMO_WINDOW: u32 = 0u;
+const EMISSIVE_M2_ADD: u32 = 1u;
+const EMISSIVE_PARTICLE_ADD: u32 = 2u;
+const EMISSIVE_MAGMA: u32 = 3u;
+
+fn tier(packed_lane: f32) -> f32 {
+ return clamp(round(packed_lane - 1.0), 0.0, 2.0);
+}
+
+fn emissive_boost(
+ color: vec3,
+ kind: u32,
+ packed_lane: f32,
+ authored_weight: f32,
+) -> vec3 {
+ let t = tier(packed_lane);
+ if (t < 0.5) {
+ return color;
+ }
+ let high = step(1.5, t);
+ var gain = mix(1.45, 1.85, high);
+ if (kind == EMISSIVE_WMO_WINDOW) { gain = mix(1.70, 2.35, high); }
+ if (kind == EMISSIVE_MAGMA) { gain = mix(1.80, 2.60, high); }
+ return color * mix(1.0, gain, clamp(authored_weight, 0.0, 1.0));
+}
+
+// Distant magma keeps part of its emissive body instead of converging completely to fog.
+fn magma_fog_resist(packed_lane: f32) -> f32 {
+ let t = tier(packed_lane);
+ return select(0.0, mix(0.32, 0.48, step(1.5, t)), t > 0.5);
+}
diff --git a/crates/benilla-assets/src/shaders/liquid.wgsl b/crates/benilla-assets/src/shaders/liquid.wgsl
index 1a9d72e7..36eb0d35 100644
--- a/crates/benilla-assets/src/shaders/liquid.wgsl
+++ b/crates/benilla-assets/src/shaders/liquid.wgsl
@@ -30,6 +30,8 @@
}
// MONKEY (enhanced water): the optional water module - see enhanced_water.wgsl and WATER.md.
#import benilla::enhanced_water::{water_active, water_swell, enhanced_water, WaterFragment}
+// MONKEY (post): shared tier-gated HDR emission and magma fog resistance.
+#import benilla::emissive_hook
@group(#{MATERIAL_BIND_GROUP}) @binding(100) var frames: texture_2d_array;
@group(#{MATERIAL_BIND_GROUP}) @binding(101) var frames_samp: sampler;
@@ -227,7 +229,12 @@ fn fragment(in: LiquidVsOut) -> @location(0) vec4 {
// Magma/slime: the sheet is the opaque body, unmodulated (the ADT vertex has no colour, the WMO
// one is `0xffffffff`) and unlit (lighting off on both paths), but fogged.
if (w.kind.x > 0.5) {
- return vec4(apply_fog(detail.rgb, in.world_position.xyz, in.room_fog), 1.0);
+ // MONKEY (post): distant magma keeps some authored body and crosses 1.0 for bloom.
+ let fogged = apply_fog(detail.rgb, in.world_position.xyz, in.room_fog);
+ let hot = emissive_hook::emissive_boost(
+ detail.rgb, emissive_hook::EMISSIVE_MAGMA, wow_light.light_diffuse.w, 1.0);
+ return vec4(mix(fogged, hot,
+ emissive_hook::magma_fog_resist(wow_light.light_diffuse.w)), 1.0);
}
// V, from the authored depth byte CPU-side: clamp(byte/42) on river/lake (LUT `0xc81768`,
diff --git a/crates/benilla-assets/src/shaders/wow_model.wgsl b/crates/benilla-assets/src/shaders/wow_model.wgsl
index 07705ba5..363d06e9 100644
--- a/crates/benilla-assets/src/shaders/wow_model.wgsl
+++ b/crates/benilla-assets/src/shaders/wow_model.wgsl
@@ -20,6 +20,8 @@
}
// MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here.
#import benilla::shadow_hook
+// MONKEY (post): shared tier-gated HDR emission; Off is an exact identity.
+#import benilla::emissive_hook
// bevy_pbr 0.18.1's `forward_io::FragmentOutput`. No depth output: a fragment depth write costs
// the pipeline early-Z, so the sky lane pins its depth in the vertex stage.
@@ -1737,6 +1739,9 @@ fn fragment(in: WowVsOut, @builtin(front_facing) is_front: bool) -> WowFragOut {
var out_rgb = rgb;
if (is_additive) {
out_rgb = out_rgb * faded_alpha;
+ // MONKEY (post): additive M2 cards become HDR before their framebuffer blend stacks them.
+ out_rgb = emissive_hook::emissive_boost(
+ out_rgb, emissive_hook::EMISSIVE_M2_ADD, wow_light.light_diffuse.w, 1.0);
}
// Mod (bit 7) and Mod2x (bit 8) read no source alpha, so the fade rides the colour as in the
// reference: texenv preset 5, `mix(prev.rgb, tex.rgb, prev.a)`, with the primary colour forced
diff --git a/crates/benilla-world/src/lighting/global_light.rs b/crates/benilla-world/src/lighting/global_light.rs
index b3373de0..4e63fa07 100644
--- a/crates/benilla-world/src/lighting/global_light.rs
+++ b/crates/benilla-world/src/lighting/global_light.rs
@@ -474,6 +474,11 @@ impl Default for FireLightGain {
}
}
+/// MONKEY (post): live `bloom` tier. The packer adds it to `light_diffuse.w`, whose old value 1
+/// was an unread clamp marker, so the 8528-byte shared buffer and every existing decode stay put.
+#[derive(Resource, Clone, Copy, Default, PartialEq, Eq, Debug)]
+pub struct EmissiveTier(pub u8);
+
/// MONKEY (spellLightGain): marks a `PointLight` that a SPELL EFFECT invented — a kit's aura glow,
/// a missile's core, an impact flash, a firework shell's burst. The app's own
/// `entities::spell_fx::SpellLight` carries the envelope, the mode and the budget; this is the
@@ -641,6 +646,8 @@ pub(super) fn register(app: &mut App) {
.init_resource::()
// MONKEY (fire GO lights): the live gain on synthesised fire lights.
.init_resource::()
+ // MONKEY (post): opt-in HDR emission, packed into an existing float lane.
+ .init_resource::()
// MONKEY (spellLightGain): and the one on spell-effect lights, which overrides it.
.init_resource::()
.init_resource::()
@@ -1286,6 +1293,8 @@ fn build_light_data(
shadow: ShadowLanes,
// MONKEY (dynamic interiors): the interior lane's on/off + live knobs, packed for `static_gx.wgsl`.
dynamic_interiors: Res,
+ // MONKEY (post): the 0/1/2 tier rides the unused `light_diffuse.w` marker.
+ emissive: Res,
// MONKEY (fire GO lights): the live gain on synthesised fire lights (0 = the lane off).
fire_gain: Res,
// MONKEY (spellLightGain): and the spell lane's own, which overrides it on a spell row.
@@ -1353,6 +1362,8 @@ fn build_light_data(
// this packs (rows 0/1, the SH block, the sun's DC redistribution) is LINEAR in that triple, so
// one multiply at the input dims all of them consistently and no derived row can be missed.
pack_model_core_rows(rows, night_dim(l.ambient), night_dim(l.diffuse), l.sun_dir);
+ // MONKEY (post): old value 1 remains tier Off; no existing shader consumes this marker.
+ rows[1][3] = 1.0 + emissive.0.min(2) as f32;
// MONKEY (world shadows): pack the world-shadow lane flag into the free `sh_c16.w` lane. The
// MCSH terrain-shadow suppression in `terrain.wgsl` keys on THIS — not on the mere presence of
// a shadow sun — so character-only shadows (sun present, world lane off) keep the baked MCSH.
@@ -2037,6 +2048,8 @@ mod tests {
.init_resource::()
.init_resource::()
.init_resource::()
+ // MONKEY (post): the packer reads the live HDR tier too.
+ .init_resource::()
.init_resource::()
.add_systems(Update, build_light_data);
app.world_mut()
diff --git a/crates/benilla-world/src/lighting/mod.rs b/crates/benilla-world/src/lighting/mod.rs
index 027a4ce0..7f745974 100644
--- a/crates/benilla-world/src/lighting/mod.rs
+++ b/crates/benilla-world/src/lighting/mod.rs
@@ -51,6 +51,8 @@ pub use global_light::WorldPointLight;
// MONKEY (spellLightGain): the spell lane's marker + its live gain — the two-word world-side
// shadow of benilla-app's own `SpellLight` lifecycle, and the dial the packer folds over it.
pub use global_light::{SpellFxLight, SpellLightGain};
+// MONKEY (post): live 0/1/2 emissive tier packed without growing the shared light blob.
+pub use global_light::EmissiveTier;
pub use global_light::{
room_claim_bytes, CLAIM_EXT_OK, LIT_ROOM_EXT_DENY, ROOM_CLAIM_MAX, ROOM_CLAIM_STRIDE,
};
diff --git a/crates/benilla-world/src/shaders/static_gx.wgsl b/crates/benilla-world/src/shaders/static_gx.wgsl
index ebe78d5a..41e12b7c 100644
--- a/crates/benilla-world/src/shaders/static_gx.wgsl
+++ b/crates/benilla-world/src/shaders/static_gx.wgsl
@@ -10,6 +10,8 @@
}
// MONKEY (shadow hook): the realtime directional-shadow term (fetch + edge/night fade) lives here.
#import benilla::shadow_hook
+// MONKEY (post): shared tier-gated HDR emission; Off is an exact identity.
+#import benilla::emissive_hook
// Group 0 is Bevy's standard mesh-view bind group (view matrices, directional-light records and
// the shadow textures the retained pass reads).
@@ -1980,6 +1982,12 @@ fn fragment(in: GxVsOut) -> @location(0) vec4 {
let factor = clamp((fog_span.y - eye_z) / denom, 0.0, 1.0);
rgb = mix(fog_color.xyz, rgb, factor);
}
+ // MONKEY (post): SIDN/window batches cross 1.0 only at night and when bloom is armed.
+ if ((in.word & WORD_WINDOW) != 0u) {
+ rgb = emissive_hook::emissive_boost(
+ rgb, emissive_hook::EMISSIVE_WMO_WINDOW, wow_light.light_diffuse.w,
+ wow_light.grade.x);
+ }
// Gamma-space output; alpha pinned 1.0, every draw here is opaque.
return vec4(rgb, 1.0);
}
diff --git a/crates/benilla-world/src/shaders/wow_effect.wgsl b/crates/benilla-world/src/shaders/wow_effect.wgsl
index f6654dd9..abf918a9 100644
--- a/crates/benilla-world/src/shaders/wow_effect.wgsl
+++ b/crates/benilla-world/src/shaders/wow_effect.wgsl
@@ -12,6 +12,8 @@
// - BLEND_MOD2X: (rgb, 1) under (Dst, Src) = `2·src·dst`, rain's state; reads no alpha.
#import bevy_render::view::View
+// MONKEY (post): shared tier-gated HDR emission; Off is an exact identity.
+#import benilla::emissive_hook
// Prefix of `lighting::global_light`'s buffer; keep in sync with wow_model.wgsl's copy.
struct WowLight {
@@ -160,7 +162,9 @@ fn fragment(in: VertexOutput) -> @location(0) vec4 {
}
#ifdef BLEND_ADD
// Premultiplied in gamma, so stacked quads sum like the reference's bytes.
- return vec4(rgb * c.a, 0.0);
+ // MONKEY (post): boost before the additive framebuffer blend, never after the stack.
+ return vec4(emissive_hook::emissive_boost(
+ rgb * c.a, emissive_hook::EMISSIVE_PARTICLE_ADD, wow_light.light_diffuse.w, 1.0), 0.0);
#else
#ifdef BLEND_OPAQUE
return vec4(rgb, 1.0);