extends Node2D ## Fog overlay controller — manages ColorRect + shader uniforms for D-059 fog. ## Reads textures from FogState autoload, positions rect to cover viewport. ## Architecture: docs/architecture/fog-shader-spec.md signal fog_noise_ready var _noise_ready: bool = false var _fog_rect: ColorRect var _shader_mat: ShaderMaterial const TILE_SIZE := float(Constants.TILE_SIZE) func _ready() -> void: # Create the fog overlay ColorRect — transparent fallback so a shader failure # doesn't paint solid white over the entire world (z:900 covers everything). _fog_rect = ColorRect.new() _fog_rect.name = "FogRect" _fog_rect.color = Color.TRANSPARENT add_child(_fog_rect) # Load shader and create material var shader := load("res://shaders/fog.gdshader") as Shader if shader == null: push_error("FogShader: failed to load res://shaders/fog.gdshader — fog disabled") return _shader_mat = ShaderMaterial.new() _shader_mat.shader = shader _fog_rect.material = _shader_mat # Create seamless noise texture for fog animation var noise := FastNoiseLite.new() noise.seed = 42 noise.noise_type = FastNoiseLite.TYPE_PERLIN noise.frequency = 0.03 var noise_tex := NoiseTexture2D.new() noise_tex.noise = noise noise_tex.width = 256 noise_tex.height = 256 noise_tex.seamless = true noise_tex.changed.connect(func(): _noise_ready = true; fog_noise_ready.emit()) _shader_mat.set_shader_parameter("noise_tex", noise_tex) _shader_mat.set_shader_parameter("tile_size", TILE_SIZE) print("FogShader: Initialized (D-059 3-state)") func update_fog() -> void: if _shader_mat == null: return # Shader failed to load — fog disabled # 1. Update FogState textures from GameState FogState.update_from_state() # 2. Position ColorRect to cover the current viewport. # Use the camera's actual position (not player position) so the fog rect # tracks the smoothed camera and never desynchronizes during smooth pan. var vp_size := get_viewport().get_visible_rect().size var cam := get_viewport().get_camera_2d() var zoom := cam.zoom if cam else Vector2(2.0, 2.0) var camera_pos := cam.global_position if cam else GameState.player_position * TILE_SIZE var half_view := vp_size / (2.0 * zoom) _fog_rect.position = camera_pos - half_view _fog_rect.size = vp_size / zoom # 3. Update shader uniforms _shader_mat.set_shader_parameter("visibility_tex", FogState.visibility_texture) _shader_mat.set_shader_parameter("exploration_tex", FogState.exploration_texture) _shader_mat.set_shader_parameter("zone_tint_tex", FogState.zone_tint_texture) _shader_mat.set_shader_parameter("rect_pos", _fog_rect.position) _shader_mat.set_shader_parameter("rect_sz", _fog_rect.size) _shader_mat.set_shader_parameter("map_offset", Vector2(FogState.map_bounds.position)) _shader_mat.set_shader_parameter("map_size", Vector2(FogState.map_bounds.size)) var t: float = FogState.override_time if FogState.override_time >= 0.0 else Time.get_ticks_msec() / 1000.0 _shader_mat.set_shader_parameter("time", t) _shader_mat.set_shader_parameter("debug_exploration", FogState.debug_exploration)