fix(client): simplify fog shader to match D-015 — light fog only, blur exploration edge

The 5-layer fog model's deep fog sub-zone (alpha 0.55-0.70) was designed
for the old peripheral sector. After #569 removed peripheral, tiles behind
the player dropped straight to deep fog — indistinguishable from unexplored
black over dark scene tiles.

D-015 is explicit: behind = light fog overlay, art preserved, just "not
fresh." There is no deep fog sub-zone.

Changes:
- Remove dual light/deep fog alpha — all explored tiles use alpha 0.25-0.35
- Add 5x5 Gaussian blur on exploration texture to soften staircase boundary
- Use blurred exploration value for the visual transition (raw for anti-bleed)
- Remove DARK_OVERLAY constant (zone_tint handles all explored color)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-01 00:22:01 +01:00
co-authored by Claude Opus 4.6
parent 95875cb92d
commit 6d416d144f
+31 -35
View File
@@ -1,10 +1,9 @@
shader_type canvas_item;
// D-059: 3-state fog shader (simplified from 5-layer by #569).
// D-059/D-015: 3-state fog shader (simplified from 5-layer by #569).
// State 1: Clear (forward cone) — transparent, soft Gaussian gradient edge (3-4 tile radius)
// State 2: Explored (out of cone, EXP_EXPLORED) — two sub-zones by vis proximity:
// a) Light fog (near cone gradient): alpha 0.25-0.35, neutral dark, 8-10s breathe
// b) Deep fog (far from cone, vis≈0): alpha 0.55-0.70, zone temperature tint, 15-20s breathe
// State 2: Explored (out of cone) — light fog overlay, alpha 0.25-0.35, zone temperature tint,
// 8-10s Perlin breathe. Art and information preserved, just "not fresh" (D-015).
// State 3: Unexplored — solid near-black #12141a
// D-033: Entity colors are NOT affected — they render above the fog overlay (z-layer 5).
// D-046: Zone temperature tint from zone_tint_tex — warm=bar, cool=hub, neutral=corridor.
@@ -23,7 +22,6 @@ uniform float time; // Seconds since start
uniform bool debug_exploration = false; // When true, render raw exploration texture
const vec3 UNEXPLORED_COLOR = vec3(0.071, 0.078, 0.102); // #12141a
const vec3 DARK_OVERLAY = vec3(0.02, 0.02, 0.05); // neutral dark (light fog zone)
// Soft gradient via 7x7 Gaussian blur on visibility (sigma 2.0).
// Spreads the cone boundary into a 3-4 tile radius gradient — no hard tile-stepped edges.
@@ -42,6 +40,23 @@ float sample_visibility(vec2 uv) {
return sum / weight;
}
// Soft gradient on exploration boundary (5x5, sigma 1.5).
// Prevents hard tile-stepped staircase at explored/unexplored edge.
float sample_exploration(vec2 uv) {
vec2 t = 1.0 / map_size;
float sum = 0.0;
float weight = 0.0;
for (float dy = -2.0; dy <= 2.0; dy += 1.0) {
for (float dx = -2.0; dx <= 2.0; dx += 1.0) {
float w = exp(-(dx * dx + dy * dy) / 4.5);
vec2 sample_uv = clamp(uv + vec2(dx, dy) * t, vec2(0.0), vec2(1.0));
sum += texture(exploration_tex, sample_uv).r * w;
weight += w;
}
}
return sum / weight;
}
void fragment() {
vec2 world_px = rect_pos + UV * rect_sz;
vec2 tile = world_px / tile_size;
@@ -69,10 +84,11 @@ void fragment() {
float vis_raw = texture(visibility_tex, tex_uv).r;
float vis = sample_visibility(tex_uv);
float explored = texture(exploration_tex, tex_uv).r;
float explored_raw = texture(exploration_tex, tex_uv).r;
float explored = sample_exploration(tex_uv);
// Prevent gradient bleed into never-explored tiles
if (explored < 0.01 && vis_raw < 0.01) {
// Prevent gradient bleed into never-explored tiles (use raw, unblurred value)
if (explored_raw < 0.01 && vis_raw < 0.01) {
vis = 0.0;
}
@@ -80,39 +96,19 @@ void fragment() {
// Unexplored: solid near-black — information zero
COLOR = vec4(UNEXPLORED_COLOR, 1.0);
} else {
// Light fog noise — fast cycle (~9s), ±0.05 breathing
float noise_fast = texture(noise_tex, tile * 0.03 + vec2(time * 0.11, time * 0.07)).r;
// Deep fog noise — slow cycle (~20s), ±0.075 breathing (more pronounced)
float noise_slow = texture(noise_tex, tile * 0.02 + vec2(time * 0.05, time * 0.035)).r;
// Fog noise — 8-10s breathe cycle, ±0.05 symmetric around baseline
float noise_val = texture(noise_tex, tile * 0.03 + vec2(time * 0.11, time * 0.07)).r;
float fog_alpha = 0.30 + (noise_val * 2.0 - 1.0) * 0.05; // 0.25-0.35
// Light fog: alpha 0.25-0.35 (centered at 0.30, ±0.05 breathe)
// Noise remapped to [-1,+1] for symmetric breathing around baseline.
float light_fog_alpha = 0.30 + (noise_fast * 2.0 - 1.0) * 0.05;
// Deep fog: alpha 0.55-0.70 (centered at 0.625, ±0.075 breathe)
float deep_fog_alpha = 0.625 + (noise_slow * 2.0 - 1.0) * 0.075;
// Blend deep <-> light fog by proximity to cone:
// vis=0 (far from cone) → deep_factor=1 → deep fog
// vis=0.3 (cone gradient) → deep_factor=0 → light fog
// vis=0.85+ (inside cone) → clarity=1 → transparent (clear)
float deep_factor = 1.0 - smoothstep(0.0, 0.30, vis);
float fog_alpha = mix(light_fog_alpha, deep_fog_alpha, deep_factor);
// Zone temperature tint (D-059/D-046/D-077):
// Deep fog color uses zone_tint_tex; light fog uses neutral DARK_OVERLAY.
// zone_tint_tex is populated per-tile from server zone_id via fog_state.gd.
// Zone temperature tint (D-046/D-077): subtle warm/cool/neutral per zone
vec3 zone_tint = texture(zone_tint_tex, tex_uv).rgb;
// Clarity ramp: transparent inside cone, fogged at edges and beyond
// Clarity ramp: transparent inside cone, light fog at edges and beyond
float clarity = smoothstep(0.0, 0.85, vis);
float alpha = mix(fog_alpha, 0.0, clarity);
vec3 color = mix(zone_tint, vec3(0.0), clarity);
// Color: deep fog → zone temperature tint; light fog → neutral dark overlay
vec3 fog_color = mix(DARK_OVERLAY, zone_tint, deep_factor);
vec3 color = mix(fog_color, vec3(0.0), clarity);
// Soft edge between explored and unexplored
// Soft edge between explored and unexplored (blurred to avoid staircase)
float exp_fade = smoothstep(0.0, 0.3, explored);
alpha = mix(1.0, alpha, exp_fade);
color = mix(UNEXPLORED_COLOR, color, exp_fade);