From 0b3ef92ab69682a7c8ce7117dfb210c917cd7110 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 7 Jun 2026 13:07:58 -0400 Subject: [PATCH] NEO-122: Separate debug HUD scroll from economy panel. Clamp HudRootScroll above the economy block, add an opaque panel fill, and shrink the economy max height so quest and recipe text no longer overlap. --- client/README.md | 8 ++-- client/scenes/main.tscn | 37 +++++++++++-------- client/scripts/main.gd | 32 ++++++++-------- .../scripts/prototype_economy_hud_section.gd | 30 +++++++++++++-- .../prototype_economy_hud_section_test.gd | 20 ++++++++++ 5 files changed, 89 insertions(+), 38 deletions(-) diff --git a/client/README.md b/client/README.md index cd16344..0942103 100644 --- a/client/README.md +++ b/client/README.md @@ -218,8 +218,8 @@ Full capstone checklist: [`docs/manual-qa/NEO-86.md`](../docs/manual-qa/NEO-86.m - **`GET /game/players/{id}/encounter-progress`** — server-authoritative per-encounter **`state`**, **`defeatedTargetIds`**, and **`rewardGrantSummary`** when **`completed`** (NEO-108); see [server README — Per-player encounter progress](../server/README.md#per-player-encounter-progress-neo-108). - **Scripts:** `scripts/encounter_progress_client.gd`; wired from `main.gd` in `_setup_encounter_progress_sync()`. - **HUD:** - - **`UICanvas/HudRoot/EncounterProgressLabel`** — frozen pocket **`prototype_combat_pocket`**: **`not started (0/3)`** → **`{n}/3`** while **`active`** → **`completed (3/3)`**. - - **`UICanvas/HudRoot/EncounterCompleteLabel`** — **`Loot: —`** until **`completed`**; then one line per grant (**`displayName`** via **`ItemDefinitionsClient`**, fallback **`itemId`**). + - **`UICanvas/HudRootScroll/HudRoot/EncounterProgressLabel`** — frozen pocket **`prototype_combat_pocket`**: **`not started (0/3)`** → **`{n}/3`** while **`active`** → **`completed (3/3)`**. + - **`UICanvas/HudRootScroll/HudRoot/EncounterCompleteLabel`** — **`Loot: —`** until **`completed`**; then one line per grant (**`displayName`** via **`ItemDefinitionsClient`**, fallback **`itemId`**). - **Refresh:** boot hydrate + **`GET`** after cast accept with **`targetDefeated: true`** (alongside gig/combat refreshes). No periodic poll or manual key. - **Inventory:** on **`completed`** row, **`InventoryLabel`** auto-refreshes (no **I**) so bag shows **`scrap_metal_bulk`** and **`contract_handoff_token`**. @@ -232,8 +232,8 @@ Full checklist: [`docs/manual-qa/NEO-110.md`](../docs/manual-qa/NEO-110.md). - **`GET /game/world/quest-definitions`** — cached on boot for display names and eligible-quest iteration (NEO-115). - **Scripts:** `scripts/quest_progress_client.gd`, `scripts/quest_definitions_client.gd`; wired from `main.gd` in `_setup_quest_progress_sync()`. - **HUD:** - - **`UICanvas/HudRoot/QuestProgressLabel`** — all four prototype quests in catalog order with compact status/step/counter lines. - - **`UICanvas/HudRoot/QuestAcceptFeedbackLabel`** — accept success/deny/failure copy. + - **`UICanvas/HudRootScroll/HudRoot/QuestProgressLabel`** — all four prototype quests in catalog order with compact status/step/counter lines. + - **`UICanvas/HudRootScroll/HudRoot/QuestAcceptFeedbackLabel`** — accept success/deny/failure copy. - **Accept keys:** **Q** = `prototype_quest_gather_intro`; **Shift+Q** = first catalog-order **`not_started`** quest with prerequisites **`completed`** in current snapshot. - **Refresh:** boot hydrate + GET after gather (interaction success), craft success, cast with **`targetDefeated: true`**, and after accept POST. No periodic poll. diff --git a/client/scenes/main.tscn b/client/scenes/main.tscn index 0698508..e102e31 100644 --- a/client/scenes/main.tscn +++ b/client/scenes/main.tscn @@ -1150,13 +1150,20 @@ theme_override_constants/outline_size = 8 theme_override_font_sizes/font_size = 22 autowrap_mode = 3 -[node name="HudRoot" type="VBoxContainer" parent="UICanvas" unique_id=9000022] +[node name="HudRootScroll" type="ScrollContainer" parent="UICanvas" unique_id=9000029] offset_left = 8.0 offset_top = 8.0 offset_right = 664.0 +offset_bottom = 392.0 +horizontal_scroll_mode = 0 +vertical_scroll_mode = 1 + +[node name="HudRoot" type="VBoxContainer" parent="UICanvas/HudRootScroll" unique_id=9000022] +layout_mode = 2 +size_flags_horizontal = 3 theme_override_constants/separation = 12 -[node name="PlayerPositionLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000003] +[node name="PlayerPositionLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000003] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.92, 0.95, 0.98, 1) @@ -1169,7 +1176,7 @@ y: — z: — srv: —" -[node name="TargetLockLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000004] +[node name="TargetLockLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000004] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.86, 0.94, 1, 1) @@ -1181,7 +1188,7 @@ text = "Target: — Validity: — Seq: —" -[node name="CastFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000005] +[node name="CastFeedbackLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000005] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.95, 0.88, 0.72, 1) @@ -1191,7 +1198,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Cast: —" -[node name="CombatTargetHpLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000018] +[node name="CombatTargetHpLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000018] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.92, 0.78, 0.82, 1) @@ -1201,7 +1208,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Target HP: — (Tab → dummy)" -[node name="PlayerCombatHpLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000019] +[node name="PlayerCombatHpLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000019] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.82, 0.92, 0.98, 1) @@ -1211,7 +1218,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Player HP: —" -[node name="EncounterProgressLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000023] +[node name="EncounterProgressLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000023] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.9, 0.82, 0.72, 1) @@ -1222,7 +1229,7 @@ autowrap_mode = 3 text = "Encounter: Loading…" -[node name="EncounterCompleteLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000024] +[node name="EncounterCompleteLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000024] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.95, 0.9, 0.65, 1) @@ -1232,7 +1239,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Loot: —" -[node name="QuestProgressLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000025] +[node name="QuestProgressLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000025] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.82, 0.92, 0.98, 1) @@ -1243,7 +1250,7 @@ autowrap_mode = 3 text = "Quests: Loading…" -[node name="QuestAcceptFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000026] +[node name="QuestAcceptFeedbackLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000026] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.9, 0.86, 1, 1) @@ -1253,7 +1260,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Quest accept: — (Q gather / Shift+Q next)" -[node name="NpcStateLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000020] +[node name="NpcStateLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000020] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.88, 0.82, 0.98, 1) @@ -1263,7 +1270,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "NPC state: —" -[node name="TelegraphLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000021] +[node name="TelegraphLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000021] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(1, 0.72, 0.55, 1) @@ -1273,7 +1280,7 @@ theme_override_font_sizes/font_size = 22 autowrap_mode = 3 text = "Telegraph: —" -[node name="GatherFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000008] +[node name="GatherFeedbackLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000008] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.88, 0.95, 0.72, 1) @@ -1283,7 +1290,7 @@ theme_override_font_sizes/font_size = 20 autowrap_mode = 3 text = "Gather: —" -[node name="CraftFeedbackLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000010] +[node name="CraftFeedbackLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000010] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.95, 0.82, 0.72, 1) @@ -1293,7 +1300,7 @@ theme_override_font_sizes/font_size = 20 autowrap_mode = 3 text = "Craft: —" -[node name="CooldownSlotsLabel" type="Label" parent="UICanvas/HudRoot" unique_id=9000006] +[node name="CooldownSlotsLabel" type="Label" parent="UICanvas/HudRootScroll/HudRoot" unique_id=9000006] layout_mode = 2 size_flags_horizontal = 3 theme_override_colors/font_color = Color(0.78, 0.92, 0.86, 1) diff --git a/client/scripts/main.gd b/client/scripts/main.gd index a1fba89..ae2ffd3 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -17,7 +17,7 @@ extends Node3D ## Steep ramp: **2 m / 1.5 m** rise/run (~53°) so slope exceeds `Player` `floor_max_angle` (~50°) ## and is not walkable; gentle ramp stays shallower than `NavigationMesh.agent_max_climb`. Steep is ## under `World` only (not nav source geometry). -## Prototype HUD: world `CharacterBody3D` position in `UICanvas/HudRoot/PlayerPositionLabel` +## Prototype HUD: world `CharacterBody3D` position in `UICanvas/HudRootScroll/HudRoot/PlayerPositionLabel` ## (updated in `_physics_process` so it matches physics ticks). const MOVE_REJECT_MSG_SECONDS: float = 4.0 @@ -128,23 +128,23 @@ var _dev_pulse_bootstrap_attempted: bool = false @onready var _interactables_root: Node3D = $World/NavigationRegion3D/InteractablesRoot @onready var _radius_preview: Node3D = $World/InteractionMarkers @onready var _move_reject_label: Label = $UICanvas/MoveRejectLabel -@onready var _hud_root: VBoxContainer = $UICanvas/HudRoot -@onready var _player_pos_label: Label = $UICanvas/HudRoot/PlayerPositionLabel -@onready var _target_lock_label: Label = $UICanvas/HudRoot/TargetLockLabel -@onready var _cast_feedback_label: Label = $UICanvas/HudRoot/CastFeedbackLabel -@onready var _combat_target_hp_label: Label = $UICanvas/HudRoot/CombatTargetHpLabel -@onready var _player_combat_hp_label: Label = $UICanvas/HudRoot/PlayerCombatHpLabel -@onready var _encounter_progress_label: Label = $UICanvas/HudRoot/EncounterProgressLabel -@onready var _encounter_complete_label: Label = $UICanvas/HudRoot/EncounterCompleteLabel -@onready var _quest_progress_label: Label = $UICanvas/HudRoot/QuestProgressLabel -@onready var _quest_accept_feedback_label: Label = $UICanvas/HudRoot/QuestAcceptFeedbackLabel -@onready var _npc_state_label: Label = $UICanvas/HudRoot/NpcStateLabel -@onready var _telegraph_label: Label = $UICanvas/HudRoot/TelegraphLabel -@onready var _cooldown_slots_label: Label = $UICanvas/HudRoot/CooldownSlotsLabel +@onready var _hud_root: VBoxContainer = $UICanvas/HudRootScroll/HudRoot +@onready var _player_pos_label: Label = $UICanvas/HudRootScroll/HudRoot/PlayerPositionLabel +@onready var _target_lock_label: Label = $UICanvas/HudRootScroll/HudRoot/TargetLockLabel +@onready var _cast_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/CastFeedbackLabel +@onready var _combat_target_hp_label: Label = $UICanvas/HudRootScroll/HudRoot/CombatTargetHpLabel +@onready var _player_combat_hp_label: Label = $UICanvas/HudRootScroll/HudRoot/PlayerCombatHpLabel +@onready var _encounter_progress_label: Label = $UICanvas/HudRootScroll/HudRoot/EncounterProgressLabel +@onready var _encounter_complete_label: Label = $UICanvas/HudRootScroll/HudRoot/EncounterCompleteLabel +@onready var _quest_progress_label: Label = $UICanvas/HudRootScroll/HudRoot/QuestProgressLabel +@onready var _quest_accept_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/QuestAcceptFeedbackLabel +@onready var _npc_state_label: Label = $UICanvas/HudRootScroll/HudRoot/NpcStateLabel +@onready var _telegraph_label: Label = $UICanvas/HudRootScroll/HudRoot/TelegraphLabel +@onready var _cooldown_slots_label: Label = $UICanvas/HudRootScroll/HudRoot/CooldownSlotsLabel @onready var _economy_hud_section: VBoxContainer = $UICanvas/EconomyHudSection @onready var _inventory_label: Label = _economy_hud_section.get_node("BodyScroll/Body/InventoryLabel") -@onready var _gather_feedback_label: Label = $UICanvas/HudRoot/GatherFeedbackLabel -@onready var _craft_feedback_label: Label = $UICanvas/HudRoot/CraftFeedbackLabel +@onready var _gather_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/GatherFeedbackLabel +@onready var _craft_feedback_label: Label = $UICanvas/HudRootScroll/HudRoot/CraftFeedbackLabel @onready var _skill_progression_label: Label = _economy_hud_section.get_node("BodyScroll/Body/SkillProgressionLabel") @onready var _gig_xp_label: Label = _economy_hud_section.get_node("BodyScroll/Body/GigXpLabel") diff --git a/client/scripts/prototype_economy_hud_section.gd b/client/scripts/prototype_economy_hud_section.gd index 45e30aa..a601ab3 100644 --- a/client/scripts/prototype_economy_hud_section.gd +++ b/client/scripts/prototype_economy_hud_section.gd @@ -1,13 +1,17 @@ extends VBoxContainer ## NEO-75: collapsible economy HUD block (inventory, skills, craft panel). -## NEO-122: pinned to viewport bottom-left so recipes stay on-screen. +## NEO-122: pinned to viewport bottom-left; clamps HudRootScroll above this panel. -const MAX_VIEWPORT_HEIGHT_FRACTION := 0.48 -const MIN_BODY_SCROLL_HEIGHT := 180.0 +const MAX_VIEWPORT_HEIGHT_FRACTION := 0.42 +const MIN_BODY_SCROLL_HEIGHT := 160.0 const BOTTOM_MARGIN := 8.0 const SIDE_MARGIN := 8.0 const PANEL_WIDTH := 656.0 +const HUD_TOP_MARGIN := 8.0 +const HUD_ECONOMY_GAP := 12.0 +const HUD_ROOT_SCROLL_NAME := &"HudRootScroll" +const PANEL_FILL := Color(0.06, 0.06, 0.1, 0.94) @onready var _toggle: CheckButton = $HeaderRow/ToggleButton @onready var _body_scroll: ScrollContainer = $BodyScroll @@ -16,6 +20,7 @@ const PANEL_WIDTH := 656.0 func _ready() -> void: z_index = 10 + clip_contents = true _toggle.button_pressed = true _body_scroll.visible = true _toggle.toggled.connect(_on_toggle_toggled) @@ -25,6 +30,10 @@ func _ready() -> void: _apply_viewport_layout() +func _draw() -> void: + draw_rect(Rect2(Vector2.ZERO, size), PANEL_FILL) + + func _on_toggle_toggled(pressed: bool) -> void: _body_scroll.visible = pressed call_deferred("_apply_viewport_layout") @@ -46,6 +55,10 @@ func scroll_body_to_recipes() -> void: _body_scroll.scroll_vertical = int(_recipes_header.position.y) +func panel_top_y() -> float: + return position.y + + func _apply_viewport_layout() -> void: if not is_instance_valid(_body_scroll): return @@ -62,6 +75,17 @@ func _apply_viewport_layout() -> void: panel_height += body_height position = Vector2(SIDE_MARGIN, vp_size.y - panel_height - BOTTOM_MARGIN) size = Vector2(PANEL_WIDTH, panel_height) + _apply_hud_root_scroll_bounds() + queue_redraw() + + +func _apply_hud_root_scroll_bounds() -> void: + var hud_scroll := get_parent().get_node_or_null(HUD_ROOT_SCROLL_NAME) + if not hud_scroll is ScrollContainer: + return + var available_height: float = position.y - HUD_TOP_MARGIN - HUD_ECONOMY_GAP + hud_scroll.position = Vector2(SIDE_MARGIN, HUD_TOP_MARGIN) + hud_scroll.size = Vector2(PANEL_WIDTH, max(80.0, available_height)) func _measure_header_height() -> float: diff --git a/client/test/prototype_economy_hud_section_test.gd b/client/test/prototype_economy_hud_section_test.gd index ebeecb4..47a6085 100644 --- a/client/test/prototype_economy_hud_section_test.gd +++ b/client/test/prototype_economy_hud_section_test.gd @@ -78,3 +78,23 @@ func test_viewport_layout_pins_section_above_bottom_margin() -> void: assert_that(section.position.y).is_greater(vp_height * 0.2) assert_that(section.position.y + section.size.y).is_less_equal(vp_height) + +func test_viewport_layout_clamps_hud_root_scroll() -> void: + # Arrange + var canvas := Control.new() + canvas.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + var hud_scroll := ScrollContainer.new() + hud_scroll.name = "HudRootScroll" + canvas.add_child(hud_scroll) + var section: Control = _build_section() as Control + canvas.add_child(section) + add_child(canvas) + await await_idle_frame() + await get_tree().process_frame + # Act + section.call("_apply_viewport_layout") + await get_tree().process_frame + # Assert + assert_that(hud_scroll.size.y).is_less(section.call("panel_top_y")) + +