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:
parent
d0f05e5622
commit
399520f0cc
1 changed files with 219 additions and 0 deletions
|
|
@ -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
|
||||
Loading…
Add table
Reference in a new issue