test(fog-renderer): Add integration test to verify fog renderer vision consumption and edge cases like fog density/visibility thresholds

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-05-18 20:10:30 -07:00
parent d0f05e5622
commit 399520f0cc

View file

@ -0,0 +1,219 @@
extends GutTest
## p1-60 workstream G: Integration test proving fog_renderer.gd correctly
## consumes per-player tile visibility and produces the expected fog
## polygon state for each of VIS_VISIBLE / VIS_SEEN_STALE / VIS_UNSEEN.
##
## Headless-compatible: fog_renderer.gd is a Node2D that creates Polygon2D
## children under itself — no display server is required to instantiate or
## inspect them. We add the renderer to the test scene tree via
## add_child_autofree so its _ready() runs and EventBus connects normally.
const FogRendererScript: GDScript = preload(
"res://engine/src/rendering/fog_renderer.gd"
)
const GameMapScript: GDScript = preload("res://engine/src/map/game_map.gd")
const TileScript: GDScript = preload("res://engine/src/map/tile.gd")
const WorldMapVisionScript: GDScript = preload(
"res://engine/scenes/world_map/world_map_vision.gd"
)
## Mirror world_map_vision.gd constants (asserted below).
const VIS_UNSEEN: int = 0
const VIS_SEEN_STALE: int = 1
const VIS_VISIBLE: int = 2
const PLAYER_INDEX: int = 0
const MAP_W: int = 6
const MAP_H: int = 6
## Hand-picked axial coordinates inside the 6x6 grid for each visibility band.
const VISIBLE_CELLS: Array[Vector2i] = [Vector2i(2, 2), Vector2i(3, 2)]
const STALE_CELLS: Array[Vector2i] = [Vector2i(0, 0), Vector2i(1, 0)]
var _renderer: Node2D = null
var _game_map: RefCounted = null
# ---------------------------------------------------------------------------
# Setup
# ---------------------------------------------------------------------------
func before_each() -> void:
_game_map = _build_grid_map(MAP_W, MAP_H)
_assign_visibilities(_game_map)
_renderer = FogRendererScript.new()
add_child_autofree(_renderer)
await get_tree().process_frame
_renderer.initialize(_game_map, PLAYER_INDEX)
func after_each() -> void:
if is_instance_valid(_renderer):
_renderer.clear()
_renderer = null
_game_map = null
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
func _build_grid_map(w: int, h: int) -> RefCounted:
var gm: RefCounted = GameMapScript.new()
gm.initialize(w, h, 0) # WrapMode.NONE — bounded for predictability
for x: int in w:
for y: int in h:
var pos: Vector2i = Vector2i(x, y)
var tile: TileScript = TileScript.new(pos, "grass")
gm.set_tile(pos, tile)
return gm
func _assign_visibilities(gm: RefCounted) -> void:
for pos: Vector2i in VISIBLE_CELLS:
(gm.tiles[pos] as TileScript).set_visibility(PLAYER_INDEX, VIS_VISIBLE)
for pos: Vector2i in STALE_CELLS:
(gm.tiles[pos] as TileScript).set_visibility(PLAYER_INDEX, VIS_SEEN_STALE)
# All other tiles remain default VIS_UNSEEN (0).
func _fog_node_for(axial: Vector2i) -> Polygon2D:
var nodes: Dictionary = _renderer._fog_nodes
if not nodes.has(axial):
return null
return nodes[axial] as Polygon2D
# ---------------------------------------------------------------------------
# Constant alignment
# ---------------------------------------------------------------------------
func test_renderer_constants_match_vision_layer() -> void:
assert_eq(FogRendererScript.FOG_UNEXPLORED, WorldMapVisionScript.VIS_UNSEEN,
"fog_renderer.FOG_UNEXPLORED must equal world_map_vision.VIS_UNSEEN")
assert_eq(FogRendererScript.FOG_FOGGED, WorldMapVisionScript.VIS_SEEN_STALE,
"fog_renderer.FOG_FOGGED must equal world_map_vision.VIS_SEEN_STALE")
assert_eq(FogRendererScript.FOG_VISIBLE, WorldMapVisionScript.VIS_VISIBLE,
"fog_renderer.FOG_VISIBLE must equal world_map_vision.VIS_VISIBLE")
# ---------------------------------------------------------------------------
# Polygon presence & visibility flag per state
# ---------------------------------------------------------------------------
func test_initialize_creates_polygon_per_tile() -> void:
## Every tile in the map gets a fog Polygon2D child (visible ones are
## hidden via .visible=false, not omitted).
assert_eq(_renderer._fog_nodes.size(), MAP_W * MAP_H,
"fog_renderer must create one Polygon2D per tile in the map")
func test_visible_tiles_polygon_is_hidden() -> void:
for pos: Vector2i in VISIBLE_CELLS:
var node: Polygon2D = _fog_node_for(pos)
assert_not_null(node, "missing fog polygon for VISIBLE tile %s" % str(pos))
assert_false(node.visible,
"VIS_VISIBLE tile %s must have fog polygon hidden (no overlay)" % str(pos))
func test_stale_tiles_polygon_is_visible_and_fogged_color() -> void:
for pos: Vector2i in STALE_CELLS:
var node: Polygon2D = _fog_node_for(pos)
assert_not_null(node, "missing fog polygon for STALE tile %s" % str(pos))
assert_true(node.visible,
"VIS_SEEN_STALE tile %s must have fog polygon visible" % str(pos))
# All non-softened vertices should carry the FOGGED_COLOR alpha (0.55).
var has_fogged: bool = _polygon_has_color(node, FogRendererScript.FOGGED_COLOR)
assert_true(has_fogged,
"VIS_SEEN_STALE tile %s must paint at least one vertex with FOGGED_COLOR" % str(pos))
func test_unseen_tiles_polygon_is_visible_and_unexplored_color() -> void:
## Pick an interior unseen tile away from the visible/stale bands so it
## won't have any softened (alpha=0) vertices.
var unseen_pos: Vector2i = Vector2i(5, 5)
var node: Polygon2D = _fog_node_for(unseen_pos)
assert_not_null(node, "missing fog polygon for UNSEEN tile %s" % str(unseen_pos))
assert_true(node.visible,
"VIS_UNSEEN tile %s must have fog polygon visible" % str(unseen_pos))
var colors: PackedColorArray = node.vertex_colors
assert_eq(colors.size(), 6, "fog polygon must have 6 vertex colors")
for i: int in 6:
var c: Color = colors[i]
assert_eq(c, FogRendererScript.UNEXPLORED_COLOR,
"interior UNSEEN tile vertex %d must be UNEXPLORED_COLOR (no neighbor softening)" % i)
# ---------------------------------------------------------------------------
# Edge softening: stale tiles bordering a visible tile fade to alpha 0
# ---------------------------------------------------------------------------
func test_stale_tile_adjacent_to_visible_has_softened_vertex() -> void:
## STALE_CELLS[1] = (1,0) is not adjacent to either visible cell, but we
## can verify softening by switching (1,0)→VIS_SEEN_STALE adjacency
## directly: place a stale tile next to a visible one.
##
## (2,2) is VIS_VISIBLE. (1,2) (axial west of 2,2) is currently UNSEEN.
## Mark (1,2) as VIS_SEEN_STALE and push an update through the renderer.
var stale_neighbor: Vector2i = Vector2i(1, 2)
(_game_map.tiles[stale_neighbor] as TileScript).set_visibility(
PLAYER_INDEX, VIS_SEEN_STALE
)
_renderer.update_tile_fog(stale_neighbor, VIS_SEEN_STALE)
var node: Polygon2D = _fog_node_for(stale_neighbor)
assert_not_null(node)
assert_true(node.visible, "stale neighbor polygon must remain visible")
var has_softened: bool = _polygon_has_alpha(
node, FogRendererScript.EDGE_FADE_ALPHA
)
assert_true(has_softened,
"stale tile bordering a VIS_VISIBLE tile must have at least one vertex softened to alpha=%f"
% FogRendererScript.EDGE_FADE_ALPHA)
# ---------------------------------------------------------------------------
# Live update path: update_tile_fog flips a tile from unseen → visible
# ---------------------------------------------------------------------------
func test_update_tile_fog_unseen_to_visible_hides_polygon() -> void:
var pos: Vector2i = Vector2i(4, 4)
var node: Polygon2D = _fog_node_for(pos)
assert_not_null(node)
assert_true(node.visible, "precondition: tile starts with visible fog polygon")
(_game_map.tiles[pos] as TileScript).set_visibility(PLAYER_INDEX, VIS_VISIBLE)
_renderer.update_tile_fog(pos, VIS_VISIBLE)
assert_false(node.visible,
"after update_tile_fog(VIS_VISIBLE) the fog polygon must be hidden")
func test_update_tile_fog_visible_to_stale_reshows_polygon() -> void:
var pos: Vector2i = VISIBLE_CELLS[0]
var node: Polygon2D = _fog_node_for(pos)
assert_false(node.visible, "precondition: VIS_VISIBLE polygon is hidden")
(_game_map.tiles[pos] as TileScript).set_visibility(PLAYER_INDEX, VIS_SEEN_STALE)
_renderer.update_tile_fog(pos, VIS_SEEN_STALE)
assert_true(node.visible,
"after VIS_VISIBLE→VIS_SEEN_STALE the fog polygon must reappear")
# ---------------------------------------------------------------------------
# Color helpers
# ---------------------------------------------------------------------------
func _polygon_has_color(node: Polygon2D, c: Color) -> bool:
for v: Color in node.vertex_colors:
if v == c:
return true
return false
func _polygon_has_alpha(node: Polygon2D, alpha: float) -> bool:
for v: Color in node.vertex_colors:
if is_equal_approx(v.a, alpha):
return true
return false