diff --git a/client/scenes/main.tscn b/client/scenes/main.tscn index 27bc6951a..f7e9ec716 100644 --- a/client/scenes/main.tscn +++ b/client/scenes/main.tscn @@ -144,12 +144,12 @@ layer = 10 ; #264: Journal panel — knowledge graph review, toggle J key, read-only (D-041) [node name="JournalPanel" parent="InsertOverlay" instance=ExtResource("25_journal")] -; HUD — implant-styled status panel (D-169, D-170). On InsertOverlay for bloom per D-051. +; HUD — implant-styled status panel (D-169, D-170). On InsertOverlay per D-049 (z-layer 6). [node name="HUD" parent="InsertOverlay" instance=ExtResource("6_hud")] ; --- UI layer (CanvasLayer 20) --- ; Monologue, cursor, stance — always visible, not affected by fog or camera. -; HUD moved to InsertOverlay (layer 10) for bloom treatment per D-051. +; HUD moved to InsertOverlay (layer 10) per D-049 (insert z-layer 6). [node name="UILayer" type="CanvasLayer" parent="."] layer = 20 @@ -168,7 +168,7 @@ layer = 20 [node name="InventoryGrid" parent="UILayer" instance=ExtResource("12_inv")] ; #511: F3 debug overlay — real-time game state, toggled by F3 -; Position: below merged HUD status panel (starts at y:16, ~80px), 10px gap +; Position: y:150 — below merged HUD status panel (y:16, ~80px tall), with gap [node name="DebugOverlay" type="Control" parent="UILayer"] anchors_preset = 0 offset_left = 16 diff --git a/client/tests/test_time_display_sprint17.gd b/client/tests/test_time_display_sprint17.gd index fbd8d1b2b..6d2162045 100644 --- a/client/tests/test_time_display_sprint17.gd +++ b/client/tests/test_time_display_sprint17.gd @@ -6,14 +6,14 @@ ## D-051 (diegetic insert display) ## ## Implementation: client/ui/time_display.gd — draw-based Control (removed from main scene #786; -## time display merged into HUD ImplantPanel at UILayer/HUD, see test_hud_* tests below). +## time display merged into HUD ImplantPanel at InsertOverlay/HUD, see test_hud_* tests below). ## Format function: Constants.format_game_time(time_of_day: int) -> String (extracted for ## testability from time_display.gd). ## ## Private state access: Tests read _time_str, _phase_str, _day_str, _has_data directly ## because time_display.gd is draw-based (no Label nodes to inspect). This is an accepted ## test pattern for draw-based UI — the private vars ARE the rendered output contract. -## Scene integration tests now check UILayer/HUD.get_time_text() instead. +## Scene integration tests now check InsertOverlay/HUD.get_time_text() instead. class_name TestTimeDisplaySprint17 extends GdUnitTestSuite @@ -273,16 +273,16 @@ func test_sim_bridge_day_phase_is_valid() -> void: # ------------------------------------------------------------------------- -# Scene: HUD panel at UILayer/HUD (time row merged in, TimeDisplay removed #786) +# Scene: HUD panel at InsertOverlay/HUD (time row merged in, TimeDisplay removed #786) # ------------------------------------------------------------------------- -func test_hud_panel_exists_in_ui_layer() -> void: +func test_hud_panel_exists_in_insert_overlay() -> void: var scene := MAIN_SCENE var instance = scene.instantiate() auto_free(instance) add_child(instance) - assert_that(instance.get_node_or_null("UILayer/HUD")).is_not_null() + assert_that(instance.get_node_or_null("InsertOverlay/HUD")).is_not_null() func test_hud_time_row_updates_after_process() -> void: var scene := MAIN_SCENE @@ -296,7 +296,7 @@ func test_hud_time_row_updates_after_process() -> void: }) instance._process(0.016) - var hud = instance.get_node_or_null("UILayer/HUD") + var hud = instance.get_node_or_null("InsertOverlay/HUD") assert_that(hud).is_not_null() assert_that(hud.get_time_text()).is_equal("12:00 · Afternoon · D1") diff --git a/client/ui/hud.gd b/client/ui/hud.gd index 049caafb9..076be411f 100644 --- a/client/ui/hud.gd +++ b/client/ui/hud.gd @@ -21,7 +21,6 @@ func _ready() -> void: _panel.name = "StatusPanel" _panel.theme_resource = theme_res _panel.custom_minimum_size.x = 200.0 - _panel.size.x = 200.0 _panel.mouse_filter = Control.MOUSE_FILTER_IGNORE _panel.position = Vector2(16, 16) # top-left; replaces standalone TimeDisplay add_child(_panel) @@ -34,10 +33,10 @@ func _ready() -> void: _panel.add_component(_health_row) _panel.add_component(_perception_row) - print("HUD: Initialized") - func update_from_hud_data(data: Dictionary) -> void: + if not _perception_row: + return if data.has("perception_mode"): var prefix := UIStrings.get_text("hud.perception_mode_prefix") if prefix.is_empty(): @@ -47,10 +46,14 @@ func update_from_hud_data(data: Dictionary) -> void: func update_health(health: int) -> void: + if not _health_row: + return _health_row.text = UIStrings.get_text("hud.health") + ": " + str(health) func update_from_state() -> void: + if not _time_row: + return var gt: Dictionary = GameState.game_time if gt.is_empty(): return diff --git a/client/ui/minimap.gd b/client/ui/minimap.gd index 246b0de42..21e40bab7 100644 --- a/client/ui/minimap.gd +++ b/client/ui/minimap.gd @@ -38,6 +38,11 @@ var _insert_active: bool = true # D-169: StyleBoxFlat matching ImplantPanel aesthetic — drawn as container frame behind the circle. var _container_style: StyleBoxFlat = null +# Dirty flag — only queue_redraw() when player position or POI list changes. +var _dirty: bool = true +var _last_player_pos: Vector2 = Vector2(INF, INF) +var _last_poi_count: int = -1 + func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -57,7 +62,16 @@ func _init_container_style() -> void: func _process(_delta: float) -> void: - if _insert_active: + if not _insert_active: + return + var pos := GameState.player_position + var poi_count := GameState.discovered_pois.size() + if pos != _last_player_pos or poi_count != _last_poi_count: + _last_player_pos = pos + _last_poi_count = poi_count + _dirty = true + if _dirty: + _dirty = false queue_redraw() @@ -66,6 +80,8 @@ func _process(_delta: float) -> void: func set_insert_active(active: bool) -> void: _insert_active = active visible = active + if active: + _dirty = true func _draw() -> void: