diff --git a/client/scripts/rendering/world_renderer.gd b/client/scripts/rendering/world_renderer.gd index 7e2d9421a..ad0c140d6 100644 --- a/client/scripts/rendering/world_renderer.gd +++ b/client/scripts/rendering/world_renderer.gd @@ -8,30 +8,29 @@ extends Node2D @onready var fog_renderer = $FogOverlay @onready var entity_renderer = $Entities -var _last_tile_count: int = 0 -var _last_visible_count: int = 0 +var _last_tick: int = -1 func _ready() -> void: print("WorldRenderer: Initialized") -# Called each frame to update visuals from game state +# Called each frame to update visuals from game state. +# Uses tick-based invalidation — re-renders all layers when a new snapshot arrives. func update_from_state() -> void: - var tile_count := GameState.visible_tiles.size() - var visible_count := GameState.visible_positions.size() + var tick := GameState.current_tick + if tick == _last_tick: + return + _last_tick = tick - # Update tiles when tile data changes (new chunks loaded, D-012) + # Update tiles (may include new chunks or modified tiles) if tile_renderer and tile_renderer.has_method("update_tiles"): - if tile_count > 0 and tile_count != _last_tile_count: + if GameState.visible_tiles.size() > 0: tile_renderer.update_tiles(GameState.visible_tiles) if fog_renderer and fog_renderer.has_method("register_tile_positions"): fog_renderer.register_tile_positions(GameState.visible_tiles) - _last_tile_count = tile_count - # Update fog overlay when visibility changes + # Update fog overlay from visibility data if fog_renderer and fog_renderer.has_method("update_fog"): - if visible_count != _last_visible_count: - fog_renderer.update_fog(GameState.visible_positions, GameState.player_position) - _last_visible_count = visible_count + fog_renderer.update_fog(GameState.visible_positions, GameState.player_position) # Update entity sprites if entity_renderer and entity_renderer.has_method("update_entities"): diff --git a/client/tests/test_rendering.gd b/client/tests/test_rendering.gd index ad28cbb82..c51fa31d8 100644 --- a/client/tests/test_rendering.gd +++ b/client/tests/test_rendering.gd @@ -31,6 +31,12 @@ func test_tile_size_constant() -> void: # -- GameState: tile and visibility data -- +func test_game_state_tick_advances() -> void: + GameState.apply_snapshot({"tick": 1, "tiles": _test_tiles}) + assert_that(GameState.current_tick).is_equal(1) + GameState.apply_snapshot({"tick": 2, "tiles": _test_tiles}) + assert_that(GameState.current_tick).is_equal(2) + func test_game_state_stores_tiles() -> void: GameState.apply_snapshot({"tick": 1, "tiles": _test_tiles}) assert_that(GameState.visible_tiles.size()).is_equal(5) @@ -49,6 +55,23 @@ func test_game_state_clears_old_visible_positions() -> void: assert_that(GameState.visible_positions.size()).is_equal(2) assert_that(GameState.visible_positions.has(Vector2i(1, 1))).is_false() +func test_game_state_same_count_different_tiles_new_tick() -> void: + var tiles_a := [{"x": 0, "y": 0, "z": 0, "type": "floor"}] + var tiles_b := [{"x": 0, "y": 0, "z": 0, "type": "door"}] + GameState.apply_snapshot({"tick": 1, "tiles": tiles_a}) + assert_that(GameState.visible_tiles[0].type).is_equal("floor") + GameState.apply_snapshot({"tick": 2, "tiles": tiles_b}) + assert_that(GameState.current_tick).is_equal(2) + assert_that(GameState.visible_tiles[0].type).is_equal("door") + +func test_game_state_same_count_different_visibility_new_tick() -> void: + GameState.apply_snapshot({"tick": 1, "visible_positions": [{"x": 1, "y": 1}]}) + assert_that(GameState.visible_positions.has(Vector2i(1, 1))).is_true() + GameState.apply_snapshot({"tick": 2, "visible_positions": [{"x": 2, "y": 2}]}) + assert_that(GameState.current_tick).is_equal(2) + assert_that(GameState.visible_positions.has(Vector2i(1, 1))).is_false() + assert_that(GameState.visible_positions.has(Vector2i(2, 2))).is_true() + func test_game_state_warns_on_missing_player() -> void: GameState.player_entity_id = 999 GameState.player_position = Vector2(5, 5)