fix(fog): fully hide undiscovered tiles on map + minimap
Undiscovered (never-seen) terrain was leaking through fog on both the main map and minimap; only the lit(visible) vs unlit(seen) distinction worked. Root cause: unexplored overlay was sub-opaque + undersized. - fog_renderer.gd: UNEXPLORED_COLOR alpha 0.85 -> 1.0 (opaque); edge-fade softening now gated to the FOGGED state only — unexplored tiles stay fully opaque even at vertices bordering a visible tile (was revealing undiscovered terrain along the exploration frontier). - design-tokens.json -> regenerated ui_theme.tres: fog.unexplored alpha 0.90 -> 1.0 (fixed at token source, not hand-edited). - minimap.gd: unexplored now drawn as a full tile-pitch opaque cover instead of a 3x3px dot (the dot left ~5px gaps at minimap scale, leaking terrain). fog.explored (seen dimming) left unchanged. Verified on apricot via iter_7q_worldmap_visual_proof with fog ENABLED: undiscovered renders solid black on map + minimap, clean hard frontier, lit tiles unaffected. (GUT cannot prove this — render-verified.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
26bf44f077
commit
c22b497a27
4 changed files with 47 additions and 24 deletions
|
|
@ -267,9 +267,9 @@
|
|||
"$description": "Seen but not currently visible tiles — 70% black"
|
||||
},
|
||||
"unexplored": {
|
||||
"$value": "#000000e5",
|
||||
"$value": "#000000ff",
|
||||
"$type": "color",
|
||||
"$description": "Never-seen tiles — 90% black"
|
||||
"$description": "Never-seen tiles — fully opaque black (undiscovered terrain must never be visible on map or minimap)"
|
||||
}
|
||||
},
|
||||
"player": {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ corner_radius_bottom_left = 2
|
|||
corner_radius_bottom_right = 2
|
||||
|
||||
[resource]
|
||||
metadata/tokens = "{\"accent.gold\":\"d9a020\",\"accent.goldBright\":\"d9b33f\",\"accent.goldPress\":\"ffd14d\",\"accent.goldResource\":\"f2d133\",\"accent.ping\":\"ffd973\",\"accent.sage\":\"66b866\",\"accent.science\":\"66bfff\",\"background.base\":\"1a1410\",\"background.deepest\":\"171219\",\"background.happiness\":\"0f0d07\",\"background.hud\":\"00000099\",\"background.list\":\"120e1e\",\"background.listSelected\":\"3f2d0d\",\"background.menu\":\"0e0a17\",\"background.overlay\":\"0000009e\",\"background.panel\":\"17121e\",\"background.raised\":\"2a2018\",\"background.surface\":\"221a14\",\"border.divider\":\"99731f80\",\"border.focus\":\"d9b340ff\",\"border.happiness\":\"b39940d9\",\"border.list\":\"4d4014b2\",\"border.listSelected\":\"d9b340cc\",\"border.panel\":\"73591fcc\",\"button.bgHover\":\"331a0d\",\"button.bgNormal\":\"1f1733\",\"button.bgPressed\":\"472f0f\",\"climate.cold\":\"1a4dff\",\"climate.hot\":\"ff260d\",\"climate.textCold\":\"66b3ff\",\"climate.textNeutral\":\"d9e0d9\",\"climate.textWarming\":\"ff731a\",\"climate.warm\":\"26cc40\",\"fog.explored\":\"000000b2\",\"fog.unexplored\":\"000000e5\",\"guide.bgPrimary\":\"1a1410\",\"guide.bgSecondary\":\"221a14\",\"guide.bgTertiary\":\"2a2018\",\"guide.dwarfAccent\":\"8b6a1a\",\"guide.dwarfPrimary\":\"c07040\",\"guide.dwarfPrimaryDark\":\"8a4a28\",\"guide.dwarfPrimaryLight\":\"e09868\",\"guide.textMuted\":\"7a6048\",\"guide.textPrimary\":\"f0e4d0\",\"guide.textSecondary\":\"b8a078\",\"player.blue\":\"3366ff\",\"player.brown\":\"806659\",\"player.cyan\":\"1accd9\",\"player.gray\":\"999999\",\"player.green\":\"33cc4d\",\"player.magenta\":\"cc4d80\",\"player.navy\":\"4d4d99\",\"player.orange\":\"e6801a\",\"player.purple\":\"b24de6\",\"player.red\":\"e63333\",\"player.sage\":\"66b366\",\"player.yellow\":\"e6cc1a\",\"semantic.diplomacy\":\"e68c73\",\"semantic.goldenAge\":\"ffeb66\",\"semantic.negative\":\"d95940\",\"semantic.positive\":\"66e666\",\"semantic.trade\":\"ccbf73\",\"semantic.warning\":\"e69933\",\"text.button\":\"e0d199\",\"text.buttonHover\":\"ffeb80\",\"text.buttonPressed\":\"ffffb3\",\"text.disabled\":\"80806680\",\"text.muted\":\"b2b2b2\",\"text.primary\":\"e0d8c8\",\"text.secondary\":\"bfb7a6\",\"text.title\":\"f2d973\"}"
|
||||
metadata/tokens = "{\"accent.gold\":\"d9a020\",\"accent.goldBright\":\"d9b33f\",\"accent.goldPress\":\"ffd14d\",\"accent.goldResource\":\"f2d133\",\"accent.ping\":\"ffd973\",\"accent.sage\":\"66b866\",\"accent.science\":\"66bfff\",\"background.base\":\"1a1410\",\"background.deepest\":\"171219\",\"background.happiness\":\"0f0d07\",\"background.hud\":\"00000099\",\"background.list\":\"120e1e\",\"background.listSelected\":\"3f2d0d\",\"background.menu\":\"0e0a17\",\"background.overlay\":\"0000009e\",\"background.panel\":\"17121e\",\"background.raised\":\"2a2018\",\"background.surface\":\"221a14\",\"border.divider\":\"99731f80\",\"border.focus\":\"d9b340ff\",\"border.happiness\":\"b39940d9\",\"border.list\":\"4d4014b2\",\"border.listSelected\":\"d9b340cc\",\"border.panel\":\"73591fcc\",\"button.bgHover\":\"331a0d\",\"button.bgNormal\":\"1f1733\",\"button.bgPressed\":\"472f0f\",\"climate.cold\":\"1a4dff\",\"climate.hot\":\"ff260d\",\"climate.textCold\":\"66b3ff\",\"climate.textNeutral\":\"d9e0d9\",\"climate.textWarming\":\"ff731a\",\"climate.warm\":\"26cc40\",\"fog.explored\":\"000000b2\",\"fog.unexplored\":\"000000ff\",\"guide.bgPrimary\":\"1a1410\",\"guide.bgSecondary\":\"221a14\",\"guide.bgTertiary\":\"2a2018\",\"guide.dwarfAccent\":\"8b6a1a\",\"guide.dwarfPrimary\":\"c07040\",\"guide.dwarfPrimaryDark\":\"8a4a28\",\"guide.dwarfPrimaryLight\":\"e09868\",\"guide.textMuted\":\"7a6048\",\"guide.textPrimary\":\"f0e4d0\",\"guide.textSecondary\":\"b8a078\",\"player.blue\":\"3366ff\",\"player.brown\":\"806659\",\"player.cyan\":\"1accd9\",\"player.gray\":\"999999\",\"player.green\":\"33cc4d\",\"player.magenta\":\"cc4d80\",\"player.navy\":\"4d4d99\",\"player.orange\":\"e6801a\",\"player.purple\":\"b24de6\",\"player.red\":\"e63333\",\"player.sage\":\"66b366\",\"player.yellow\":\"e6cc1a\",\"semantic.diplomacy\":\"e68c73\",\"semantic.goldenAge\":\"ffeb66\",\"semantic.negative\":\"d95940\",\"semantic.positive\":\"66e666\",\"semantic.trade\":\"ccbf73\",\"semantic.warning\":\"e69933\",\"text.button\":\"e0d199\",\"text.buttonHover\":\"ffeb80\",\"text.buttonPressed\":\"ffffb3\",\"text.disabled\":\"80806680\",\"text.muted\":\"b2b2b2\",\"text.primary\":\"e0d8c8\",\"text.secondary\":\"bfb7a6\",\"text.title\":\"f2d973\"}"
|
||||
Button/colors/font_color = Color(0.878431, 0.819608, 0.6, 1)
|
||||
Button/colors/font_hover_color = Color(1, 0.921569, 0.501961, 1)
|
||||
Button/colors/font_pressed_color = Color(1, 1, 0.701961, 1)
|
||||
|
|
|
|||
|
|
@ -222,8 +222,9 @@ func _draw_ping() -> void:
|
|||
var color: Color = _ping_ring_color
|
||||
color.a = 1.0 - t
|
||||
var pixel_pos: Vector2 = _world_to_mini(
|
||||
HexUtilsScript.axial_to_pixel(_ping_axial) + Vector2(
|
||||
HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5
|
||||
(
|
||||
HexUtilsScript.axial_to_pixel(_ping_axial)
|
||||
+ Vector2(HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5)
|
||||
)
|
||||
)
|
||||
_overlay_rect.draw_arc(pixel_pos, radius, 0.0, TAU, 24, color, PING_RING_WIDTH)
|
||||
|
|
@ -235,14 +236,29 @@ func _draw_fog(game_map: RefCounted, player_index: int) -> void:
|
|||
if vis == 2:
|
||||
continue
|
||||
var pixel_pos: Vector2 = _world_to_mini(
|
||||
HexUtilsScript.axial_to_pixel(axial) + Vector2(
|
||||
HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5
|
||||
(
|
||||
HexUtilsScript.axial_to_pixel(axial)
|
||||
+ Vector2(HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5)
|
||||
)
|
||||
)
|
||||
var fog_col: Color = _fog_color if vis == 1 else _unexplored_color
|
||||
_overlay_rect.draw_rect(
|
||||
Rect2(pixel_pos - Vector2(1.0, 1.0), Vector2(3.0, 3.0)), fog_col
|
||||
)
|
||||
if vis == 1:
|
||||
# Seen-but-stale: light dot overlay — terrain stays partly visible.
|
||||
_overlay_rect.draw_rect(
|
||||
Rect2(pixel_pos - Vector2(1.0, 1.0), Vector2(3.0, 3.0)), _fog_color
|
||||
)
|
||||
else:
|
||||
# Unexplored: cover the tile's full minimap footprint with opaque
|
||||
# black. The 3px dot left ~5px gaps at this scale, leaking
|
||||
# undiscovered terrain; size the rect to the tile pitch (+2px overlap
|
||||
# to kill seams) so never-seen territory is fully hidden.
|
||||
var cover: Vector2 = (
|
||||
Vector2(
|
||||
HexUtilsScript.HEX_HORIZ_SPACING * _minimap_scale.x,
|
||||
HexUtilsScript.HEX_VERT_SPACING * _minimap_scale.y
|
||||
)
|
||||
+ Vector2(2.0, 2.0)
|
||||
)
|
||||
_overlay_rect.draw_rect(Rect2(pixel_pos - cover * 0.5, cover), _unexplored_color)
|
||||
|
||||
|
||||
func _draw_owner_tiles(game_map: RefCounted) -> void:
|
||||
|
|
@ -261,8 +277,9 @@ func _draw_owner_tiles(game_map: RefCounted) -> void:
|
|||
var tint: Color = player_colors[owner_idx]
|
||||
tint.a = OWNER_TINT_ALPHA
|
||||
var pixel_pos: Vector2 = _world_to_mini(
|
||||
HexUtilsScript.axial_to_pixel(axial) + Vector2(
|
||||
HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5
|
||||
(
|
||||
HexUtilsScript.axial_to_pixel(axial)
|
||||
+ Vector2(HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5)
|
||||
)
|
||||
)
|
||||
_overlay_rect.draw_rect(Rect2(pixel_pos - OWNER_TILE_SIZE * 0.5, OWNER_TILE_SIZE), tint)
|
||||
|
|
@ -281,8 +298,9 @@ func _draw_city_dots(player: Variant) -> void:
|
|||
for city: Variant in player.cities:
|
||||
var city_pos: Vector2i = _entity_position(city)
|
||||
var pixel_pos: Vector2 = _world_to_mini(
|
||||
HexUtilsScript.axial_to_pixel(city_pos) + Vector2(
|
||||
HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5
|
||||
(
|
||||
HexUtilsScript.axial_to_pixel(city_pos)
|
||||
+ Vector2(HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5)
|
||||
)
|
||||
)
|
||||
_overlay_rect.draw_circle(pixel_pos, CITY_DOT_RADIUS, player.color)
|
||||
|
|
@ -293,8 +311,9 @@ func _draw_unit_dots(player: Variant) -> void:
|
|||
for unit: Variant in player.units:
|
||||
var unit_pos: Vector2i = _entity_position(unit)
|
||||
var pixel_pos: Vector2 = _world_to_mini(
|
||||
HexUtilsScript.axial_to_pixel(unit_pos) + Vector2(
|
||||
HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5
|
||||
(
|
||||
HexUtilsScript.axial_to_pixel(unit_pos)
|
||||
+ Vector2(HexUtilsScript.HEX_WIDTH * 0.5, HexUtilsScript.HEX_HEIGHT * 0.5)
|
||||
)
|
||||
)
|
||||
_overlay_rect.draw_circle(pixel_pos, UNIT_DOT_RADIUS, player.color)
|
||||
|
|
@ -331,9 +350,7 @@ func _draw_viewport_rect() -> void:
|
|||
if rect.size.x < 2.0 or rect.size.y < 2.0:
|
||||
return
|
||||
|
||||
var vp_color: Color = _parse_color(
|
||||
EnvConfig.get_var("MINIMAP_VIEWPORT_COLOR", "#FFFFFFB3")
|
||||
)
|
||||
var vp_color: Color = _parse_color(EnvConfig.get_var("MINIMAP_VIEWPORT_COLOR", "#FFFFFFB3"))
|
||||
var vp_width: float = EnvConfig.get_var("MINIMAP_VIEWPORT_WIDTH", "1.5").to_float()
|
||||
_overlay_rect.draw_rect(rect, vp_color, false, vp_width)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const FOG_UNEXPLORED: int = 0
|
|||
const FOG_FOGGED: int = 1
|
||||
const FOG_VISIBLE: int = 2
|
||||
|
||||
const UNEXPLORED_COLOR: Color = Color(0.0, 0.0, 0.0, 0.85)
|
||||
const UNEXPLORED_COLOR: Color = Color(0.0, 0.0, 0.0, 1.0)
|
||||
const FOGGED_COLOR: Color = Color(0.0, 0.0, 0.0, 0.55)
|
||||
## Alpha at vertices that border a visible tile (soft edge fade).
|
||||
const EDGE_FADE_ALPHA: float = 0.0
|
||||
|
|
@ -133,6 +133,14 @@ func _build_vertex_colors(axial: Vector2i, visibility_state: int) -> PackedColor
|
|||
var base: Color = _get_fog_color(visibility_state)
|
||||
var colors: PackedColorArray = PackedColorArray()
|
||||
colors.resize(6)
|
||||
# Edge-softening only applies to the FOGGED (seen) state — it creates the
|
||||
# smooth gradient at the visible↔seen boundary. Unexplored tiles must stay
|
||||
# fully opaque even at vertices bordering a visible tile, otherwise
|
||||
# undiscovered terrain bleeds through along the exploration frontier.
|
||||
if visibility_state != FOG_FOGGED:
|
||||
for i: int in 6:
|
||||
colors[i] = base
|
||||
return colors
|
||||
for i: int in 6:
|
||||
var softened: bool = false
|
||||
for dir_idx: int in VERTEX_NEIGHBOR_DIRS[i]:
|
||||
|
|
@ -192,9 +200,7 @@ func _get_axial_visibility(axial: Vector2i) -> int:
|
|||
return _get_tile_visibility(tile)
|
||||
|
||||
|
||||
func _on_tile_visibility_changed(
|
||||
tile_pos: Vector2i, player_index: int, state: int
|
||||
) -> void:
|
||||
func _on_tile_visibility_changed(tile_pos: Vector2i, player_index: int, state: int) -> void:
|
||||
if player_index != _player_index:
|
||||
return
|
||||
update_tile_fog(tile_pos, state)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue