From 6cc5db09fcedc30251d3d53080830085b4139888 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 21 Jun 2026 01:08:10 +0900 Subject: [PATCH] Improve battle readability and post-move commands --- data/scenarios/001_yellow_turbans.json | 10 ++-- scripts/core/battle_state.gd | 1 + scripts/scenes/battle_scene.gd | 74 ++++++++++++++++---------- tools/smoke_post_move_action_flow.gd | 46 +++++++++++++--- tools/smoke_visual_assets.gd | 42 +++++++++++---- 5 files changed, 125 insertions(+), 48 deletions(-) diff --git a/data/scenarios/001_yellow_turbans.json b/data/scenarios/001_yellow_turbans.json index 143e4e3..3337526 100644 --- a/data/scenarios/001_yellow_turbans.json +++ b/data/scenarios/001_yellow_turbans.json @@ -266,11 +266,11 @@ "GRRRRRTTRRRGGGGGGHHHHG", "GGGGGGGGRRRRRRRRGGGGGG", "GGGGGGGGGGGGGTTGGGGGGG", - "GGGGGGGGGGDDDTTGGGGGGG", - "GGGWWWGGGGDDDDGGGGGGGG", - "GGGWWWGGGGDDDDGGGGGGGG", - "GGGGWWWGGGGGGGGGGGGGGG", - "GGGGGGGGGGGGGGGGGGGGGG" + "GGGWGGGGGGDDDTTGGGGGGG", + "GGWWWWGGGGDDDDGGGGGGGG", + "GGWWWWGGGGDDDDGGGGGGGG", + "GGGWWWWGGGGGGGGGGGGGGG", + "GGGGWWWGGGGGGGGGGGGGGG" ] }, "deployments": [ diff --git a/scripts/core/battle_state.gd b/scripts/core/battle_state.gd index be1d2fb..6af989f 100644 --- a/scripts/core/battle_state.gd +++ b/scripts/core/battle_state.gd @@ -3858,6 +3858,7 @@ func _grant_experience(unit: Dictionary, amount: int) -> void: return unit["exp"] = int(unit.get("exp", 0)) + amount _emit_log("%s 공적 +%d." % [unit["name"], amount]) + _emit_combat_feedback(unit, "공적 +%d" % amount, "progress") while int(unit["exp"]) >= EXP_LEVEL: unit["exp"] = int(unit["exp"]) - EXP_LEVEL _level_up(unit) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index 7e7f4b9..0282b7d 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -238,10 +238,10 @@ const BATTLE_UNIT_LIST_BADGE_SIZE := Vector2(72, 22) const BATTLE_UNIT_LIST_BADGE_ICON_SIZE := Vector2(15, 15) const BATTLE_UNIT_LIST_HP_BAR_SIZE := Vector2(40, 7) const SETTINGS_PANEL_SIZE := Vector2(420, 414) -const LOCAL_COMMAND_BUTTON_SIZE := Vector2(44, 38) -const LOCAL_COMMAND_CANCEL_BUTTON_SIZE := Vector2(196, 30) +const LOCAL_COMMAND_BUTTON_SIZE := Vector2(62, 42) +const LOCAL_COMMAND_CANCEL_BUTTON_SIZE := Vector2(244, 30) const LOCAL_COMMAND_HEADER_ICON_SIZE := Vector2(24, 24) -const POST_MOVE_MENU_SIZE := Vector2(232, 156) +const POST_MOVE_MENU_SIZE := Vector2(292, 162) const POST_MOVE_TITLE_SIZE := Vector2(126, 24) const POST_MOVE_PICKER_PANEL_SIZE := Vector2(352, 238) const POST_MOVE_PICKER_TITLE_SIZE := Vector2(312, 24) @@ -3293,17 +3293,29 @@ func _make_screen_backdrop() -> ColorRect: backdrop.visible = false backdrop.set_anchors_preset(Control.PRESET_FULL_RECT) backdrop.color = Color(0.018, 0.009, 0.004, 0.84) - backdrop.mouse_filter = Control.MOUSE_FILTER_IGNORE + backdrop.mouse_filter = Control.MOUSE_FILTER_STOP return backdrop func _refresh_screen_backdrop_visibility() -> void: if screen_backdrop == null: return - screen_backdrop.visible = ( - (briefing_panel != null and briefing_panel.visible) - or (result_panel != null and result_panel.visible) - ) + var front_modal: Control = null + if dialogue_panel != null and dialogue_panel.visible: + front_modal = dialogue_panel + screen_backdrop.color = Color(0.010, 0.009, 0.008, 0.64) + elif briefing_panel != null and briefing_panel.visible: + front_modal = briefing_panel + screen_backdrop.color = Color(0.018, 0.009, 0.004, 0.84) + elif result_panel != null and result_panel.visible: + front_modal = result_panel + screen_backdrop.color = Color(0.018, 0.009, 0.004, 0.84) + else: + screen_backdrop.color = Color(0.018, 0.009, 0.004, 0.84) + screen_backdrop.visible = front_modal != null + if screen_backdrop.visible: + screen_backdrop.move_to_front() + front_modal.move_to_front() func _create_hud() -> void: @@ -4063,7 +4075,8 @@ func _create_hud() -> void: "공격", "적 선택", LOCAL_COMMAND_BUTTON_SIZE, - "공격 가능한 적에게 붉은 표식이 뜹니다. 표식을 누르면 이동과 공격을 한 번에 끝냅니다." + "공격 가능한 적에게 붉은 표식이 뜹니다. 표식을 누르면 이동과 공격을 한 번에 끝냅니다.", + false ) post_move_attack_button.pressed.connect(_on_post_move_attack_pressed) post_move_grid.add_child(post_move_attack_button) @@ -4075,7 +4088,8 @@ func _create_hud() -> void: "책략", "책략 선택", LOCAL_COMMAND_BUTTON_SIZE, - "쓸 책략을 고른 뒤 빛나는 표식 칸을 지목합니다." + "쓸 책략을 고른 뒤 빛나는 표식 칸을 지목합니다.", + false ) post_move_tactic_button.pressed.connect(_on_post_move_tactic_pressed) post_move_grid.add_child(post_move_tactic_button) @@ -4087,7 +4101,8 @@ func _create_hud() -> void: "도구", "도구 선택", LOCAL_COMMAND_BUTTON_SIZE, - "쓸 도구를 고른 뒤 회복이나 효과를 받을 부대를 지목합니다." + "쓸 도구를 고른 뒤 회복이나 효과를 받을 부대를 지목합니다.", + false ) post_move_item_button.pressed.connect(_on_post_move_item_pressed) post_move_grid.add_child(post_move_item_button) @@ -4099,7 +4114,8 @@ func _create_hud() -> void: "대기", "행동 종료", LOCAL_COMMAND_BUTTON_SIZE, - "이동한 자리에서 이 부대의 행동을 마칩니다." + "이동한 자리에서 이 부대의 행동을 마칩니다.", + false ) post_move_wait_button.pressed.connect(_on_post_move_wait_pressed) post_move_grid.add_child(post_move_wait_button) @@ -6266,7 +6282,7 @@ func _draw_recovery_marker_amount(font: Font, marker_rect: Rect2, marker_text: S func _recovery_tile_marker_modulate(cell: Vector2i) -> Color: var heal := maxi(0, state.get_terrain_heal(cell)) - var alpha := 0.42 + minf(float(heal) * 0.012, 0.12) + var alpha := 0.18 + minf(float(heal) * 0.007, 0.07) return Color(1.0, 1.0, 1.0, alpha) @@ -6766,12 +6782,12 @@ func _draw_terrain_feature(feature_key: String, rect: Rect2, modulate: Color = C func _draw_tactical_tile_overlay(marker_key: String, rect: Rect2, fill_color: Color, border_color: Color, marker_alpha: float, fill_alpha_scale: float = 0.48, inset: float = 5.0) -> void: var fill := fill_color - fill.a *= fill_alpha_scale * 0.58 + fill.a *= fill_alpha_scale * 0.42 if fill.a > 0.0: draw_rect(rect.grow(-inset), fill) - _draw_tile_marker(marker_key, rect, Color(1.0, 1.0, 1.0, marker_alpha * 1.12), 0.0) + _draw_tile_marker(marker_key, rect, Color(1.0, 1.0, 1.0, marker_alpha * 0.72), 0.0) var line := border_color - line.a = minf(line.a * 0.78, 0.48) + line.a = minf(line.a * 0.66, 0.38) _draw_tactical_corner_brackets(rect.grow(-(inset + 2.0)), line, 7.0, 1.15) @@ -6794,15 +6810,15 @@ func _draw_tactical_corner_brackets(rect: Rect2, color: Color, length: float = 8 func _terrain_texture_modulate(cell: Vector2i, terrain_key: String, has_background: bool) -> Color: - var alpha := 0.20 if has_background else 0.58 + var alpha := 0.12 if has_background else 0.58 if terrain_key == "W": - alpha += 0.06 if has_background else 0.10 + alpha += 0.04 if has_background else 0.10 elif terrain_key == "C" or terrain_key == "T": - alpha += 0.07 if has_background else 0.12 + alpha += 0.05 if has_background else 0.12 elif terrain_key == "R": - alpha += 0.04 if has_background else 0.08 + alpha += 0.025 if has_background else 0.08 elif terrain_key == "F" or terrain_key == "H" or terrain_key == "D": - alpha += 0.025 if has_background else 0.06 + alpha += 0.018 if has_background else 0.06 alpha *= 0.94 + _cell_noise(cell, 137) * 0.12 return Color(1.0, 1.0, 1.0, clampf(alpha, 0.0, 0.82)) @@ -6827,21 +6843,21 @@ func _draw_terrain_detail(cell: Vector2i, rect: Rect2, terrain_key: String) -> v draw_line(peak, point + Vector2(12, 9), Color(0.20, 0.18, 0.14, 0.42), 2.2) draw_line(point + Vector2(-5, 5), point + Vector2(5, 6), Color(0.10, 0.09, 0.07, 0.20), 1.1) elif terrain_key == "D": - if not _draw_terrain_feature("wasteland", rect, Color(1.0, 1.0, 1.0, 0.68)): + if not _draw_terrain_feature("wasteland", rect, Color(1.0, 1.0, 1.0, 0.48)): _draw_wasteland_detail(cell, rect) elif terrain_key == "R": _draw_road_detail(cell, rect) elif terrain_key == "W": - if not _draw_terrain_feature("water", rect, Color(1.0, 1.0, 1.0, 0.78)): + if not _draw_terrain_feature("water", rect, Color(1.0, 1.0, 1.0, 0.54)): for index in range(3): var y := rect.position.y + 15.0 + float(index) * 14.0 draw_line(rect.position + Vector2(10, y - rect.position.y), rect.position + Vector2(rect.size.x - 10, y - rect.position.y + 4), Color(0.70, 0.88, 1.0, 0.32), 1.6) _draw_water_shoreline(cell, rect) elif terrain_key == "C": - if not _draw_terrain_feature("castle", rect, Color(1.0, 1.0, 1.0, 0.74)): + if not _draw_terrain_feature("castle", rect, Color(1.0, 1.0, 1.0, 0.54)): _draw_castle_detail(rect) elif terrain_key == "T": - if not _draw_terrain_feature("town", rect, Color(1.0, 1.0, 1.0, 0.74)): + if not _draw_terrain_feature("town", rect, Color(1.0, 1.0, 1.0, 0.54)): _draw_town_detail(rect) else: for index in range(2): @@ -6850,7 +6866,7 @@ func _draw_terrain_detail(cell: Vector2i, rect: Rect2, terrain_key: String) -> v func _draw_road_detail(cell: Vector2i, rect: Rect2) -> void: - if _draw_terrain_feature(_road_feature_key(cell), rect, Color(1.0, 1.0, 1.0, 0.82)): + if _draw_terrain_feature(_road_feature_key(cell), rect, Color(1.0, 1.0, 1.0, 0.56)): return var road_color := Color(0.82, 0.69, 0.48, 0.34) var rut_color := Color(0.36, 0.28, 0.18, 0.24) @@ -7141,8 +7157,8 @@ func _draw_event_foreground_marker(rect: Rect2, label: String, kind: String, fon var color := _event_marker_color(kind) var tile_marker_key := _event_marker_tile_marker_key(label, kind) if not tile_marker_key.is_empty(): - _draw_tile_marker(tile_marker_key, rect, Color(1.0, 1.0, 1.0, 0.38), 2.0) - draw_rect(rect.grow(-8.0), Color(color.r, color.g, color.b, 0.16), false, 1.5) + _draw_tile_marker(tile_marker_key, rect, Color(1.0, 1.0, 1.0, 0.18), 5.0) + draw_rect(rect.grow(-8.0), Color(color.r, color.g, color.b, 0.08), false, 1.1) var marker_rect := _event_marker_rect(rect) draw_rect(marker_rect, Color(0.035, 0.022, 0.012, 0.76)) draw_rect(marker_rect.grow(-2.0), Color(color.r, color.g, color.b, 0.72)) @@ -12932,6 +12948,7 @@ func _render_dialogue_line() -> void: _update_dialogue_portrait(display_speaker, str(line.get("portrait", ""))) _update_dialogue_controls() dialogue_panel.visible = true + _refresh_screen_backdrop_visibility() _update_hud() queue_redraw() @@ -13097,6 +13114,7 @@ func _hide_dialogue_panel() -> void: dialogue_progress_label.text = "" if dialogue_previous_button != null: dialogue_previous_button.disabled = true + _refresh_screen_backdrop_visibility() func _clear_dialogue() -> void: diff --git a/tools/smoke_post_move_action_flow.gd b/tools/smoke_post_move_action_flow.gd index 5410a28..c57774d 100644 --- a/tools/smoke_post_move_action_flow.gd +++ b/tools/smoke_post_move_action_flow.gd @@ -218,13 +218,14 @@ func _check_scene_post_move_menu_flow(failures: Array[String]) -> void: if scene.post_move_menu == null or not scene.post_move_menu.visible: failures.append("post-move action menu should be visible after moving") else: - if BattleSceneScript.POST_MOVE_MENU_SIZE.y > 160.0: + if BattleSceneScript.POST_MOVE_MENU_SIZE.y > 170.0: failures.append("post-move action menu should stay compact around the unit: %s" % str(BattleSceneScript.POST_MOVE_MENU_SIZE)) var command_strip := scene.post_move_menu.find_child("PostMoveCommandStrip", true, false) as GridContainer if command_strip == null or command_strip.columns != 4: failures.append("post-move action menu should present four primary commands in one compact strip") if scene.post_move_cancel_button == null or scene.post_move_cancel_button.text != "이동 취소": failures.append("post-move cancel should remain a readable text command") + _check_visible_post_move_command_labels(failures, scene) _check_local_panel_position(failures, scene, scene.post_move_menu, Vector2i(2, 3), "post-move action menu") _check_local_command_button_tooltips_present(failures, [ scene.post_move_attack_button, @@ -515,11 +516,11 @@ func _check_scene_post_move_blocked_command_labels(failures: Array[String]) -> v if scene.post_move_menu == null or not scene.post_move_menu.visible: failures.append("blocked command label setup should show the post-move menu") else: - if scene.post_move_attack_button == null or not scene.post_move_attack_button.disabled or scene.post_move_attack_button.text != "" or str(scene.post_move_attack_button.get_meta("disabled_hint", "")) != "공격 없음": + if scene.post_move_attack_button == null or not scene.post_move_attack_button.disabled or scene.post_move_attack_button.text != "공격" or str(scene.post_move_attack_button.get_meta("disabled_hint", "")) != "공격 없음": failures.append("blocked attack command should explain itself through local hover metadata") - if scene.post_move_tactic_button == null or not scene.post_move_tactic_button.disabled or scene.post_move_tactic_button.text != "" or str(scene.post_move_tactic_button.get_meta("disabled_hint", "")) != "책략 없음": + if scene.post_move_tactic_button == null or not scene.post_move_tactic_button.disabled or scene.post_move_tactic_button.text != "책략" or str(scene.post_move_tactic_button.get_meta("disabled_hint", "")) != "책략 없음": failures.append("blocked tactic command should explain itself through local hover metadata") - if scene.post_move_item_button == null or not scene.post_move_item_button.disabled or scene.post_move_item_button.text != "" or str(scene.post_move_item_button.get_meta("disabled_hint", "")) != "도구 없음": + if scene.post_move_item_button == null or not scene.post_move_item_button.disabled or scene.post_move_item_button.text != "도구" or str(scene.post_move_item_button.get_meta("disabled_hint", "")) != "도구 없음": failures.append("blocked item command should explain itself through local hover metadata") _check_local_command_button_tooltips_present(failures, [ scene.post_move_attack_button, @@ -576,8 +577,8 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void: scene._handle_board_click(_screen_for_cell(Vector2i(2, 3))) if not scene._has_pending_move() or scene.post_move_menu == null or not scene.post_move_menu.visible: failures.append("attack targeting setup should leave the post-move menu visible") - if scene.post_move_attack_button == null or scene.post_move_attack_button.disabled or scene.post_move_attack_button.text != "" or str(scene.post_move_attack_button.get_meta("command_label", "")) != "공격": - failures.append("available attack command should stay icon-first and enabled") + if scene.post_move_attack_button == null or scene.post_move_attack_button.disabled or scene.post_move_attack_button.text != "공격" or str(scene.post_move_attack_button.get_meta("command_label", "")) != "공격": + failures.append("available attack command should stay readable and enabled") scene._on_local_command_button_mouse_entered(scene.post_move_attack_button) if scene.post_move_title_label == null or not scene.post_move_title_label.text.contains("공격"): failures.append("available attack hover should explain the icon in the local title plate") @@ -613,6 +614,11 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void: if cao_cao.get("pos", Vector2i.ZERO) != Vector2i(2, 3) or not bool(cao_cao.get("moved", false)) or bool(cao_cao.get("acted", false)): failures.append("Back should preserve the pending moved unit without acting") + var progress_feedback: Array[String] = [] + scene.state.combat_feedback_requested.connect(func(unit_id: String, text: String, kind: String) -> void: + if unit_id == "cao_cao" and kind == "progress": + progress_feedback.append(text) + ) scene._on_post_move_attack_pressed() var enemy_hp_before := int(enemy.get("hp", 0)) scene._handle_board_click(_screen_for_cell(Vector2i(3, 3))) @@ -628,6 +634,8 @@ func _check_scene_post_move_attack_targeting(failures: Array[String]) -> void: failures.append("attack click should damage or defeat the target") if scene.state.get_battle_gold_reward() != 33: failures.append("attack click should commit deferred movement event before resolving attack") + if not _feedback_contains(progress_feedback, "공적 +"): + failures.append("attack click should show visible merit feedback over the unit: %s" % str(progress_feedback)) scene.free() @@ -1164,6 +1172,32 @@ func _check_local_command_button_tooltips_present(failures: Array[String], butto failures.append("%s should explain icon function through hover tooltip" % label) +func _check_visible_post_move_command_labels(failures: Array[String], scene) -> void: + var expectations := [ + {"button": scene.post_move_attack_button, "label": "공격"}, + {"button": scene.post_move_tactic_button, "label": "책략"}, + {"button": scene.post_move_item_button, "label": "도구"}, + {"button": scene.post_move_wait_button, "label": "대기"} + ] + for expectation in expectations: + var button: Button = expectation["button"] + var label := str(expectation["label"]) + if button == null: + failures.append("post-move command button missing visible label: %s" % label) + continue + if button.text != label: + failures.append("post-move command should show readable text beside its icon: %s text=%s" % [label, button.text]) + if bool(button.get_meta("icon_only", false)): + failures.append("post-move command should not be icon-only once the unit has moved: %s" % label) + + +func _feedback_contains(feedback: Array[String], needle: String) -> bool: + for text in feedback: + if text.contains(needle): + return true + return false + + func _check_post_move_command_guidance(failures: Array[String], scene) -> void: var expectations := [ { diff --git a/tools/smoke_visual_assets.gd b/tools/smoke_visual_assets.gd index 5bc52ad..7f7a825 100644 --- a/tools/smoke_visual_assets.gd +++ b/tools/smoke_visual_assets.gd @@ -3318,10 +3318,10 @@ func _check_map_overlay_softness(failures: Array[String]) -> void: if fallback_grid.a > 0.24: failures.append("fallback grid should not become a harsh square net, got %.3f" % fallback_grid.a) var grass_texture: Color = scene._terrain_texture_modulate(Vector2i(3, 4), "G", true) - if grass_texture.a > 0.235: + if grass_texture.a > 0.165: failures.append("background-backed grass texture should blend into the high-res map, got %.3f" % grass_texture.a) var town_texture: Color = scene._terrain_texture_modulate(Vector2i(7, 5), "T", true) - if town_texture.a > 0.310: + if town_texture.a > 0.225: failures.append("background-backed town texture should signal terrain without tiling loudly, got %.3f" % town_texture.a) var fallback_texture: Color = scene._terrain_texture_modulate(Vector2i(3, 4), "G", false) if fallback_texture.a < 0.50: @@ -3663,8 +3663,8 @@ func _check_hud_focus_text(failures: Array[String]) -> void: if not marker_rect.encloses(recovery_amount_rect): failures.append("Recovery terrain amount badge should sit inside generated marker: %s in %s" % [str(recovery_amount_rect), str(marker_rect)]) var recovery_tile_modulate: Color = scene._recovery_tile_marker_modulate(Vector2i(6, 5)) - if recovery_tile_modulate.a < 0.45: - failures.append("Recovery terrain tile marker should be visible over high-res map art: %.3f" % recovery_tile_modulate.a) + if recovery_tile_modulate.a < 0.16 or recovery_tile_modulate.a > 0.28: + failures.append("Recovery terrain tile marker should stay subtle but visible over high-res map art: %.3f" % recovery_tile_modulate.a) var facing_tile_rect: Rect2 = scene._rect_for_cell(Vector2i(1, 6)) var facing_marker_points: PackedVector2Array = scene._unit_facing_marker_points(facing_tile_rect, cao_cao) var facing_points_inside := facing_marker_points.size() == 3 @@ -4022,7 +4022,13 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("dialogue speaker label should render localized display_speaker") if scene.dialogue_text_label == null or scene.dialogue_text_label.text != "「군령을 전하라.」": failures.append("dialogue text should render as a concise quotation scroll") + if scene.screen_backdrop == null or not scene.screen_backdrop.visible: + failures.append("dialogue should dim and lock the battlefield behind the text panel") + elif scene.screen_backdrop.color.a < 0.55 or scene.screen_backdrop.color.a > 0.72: + failures.append("dialogue backdrop should be readable without blacking out portraits: %s" % str(scene.screen_backdrop.color)) scene._hide_dialogue_panel() + if scene.screen_backdrop != null and scene.screen_backdrop.visible: + failures.append("dialogue backdrop should hide after closing the dialogue panel") var narrator_lines := scene._normalized_dialogue_lines([{"text": "성채 위로 먼지가 올랐다."}]) if narrator_lines.is_empty(): failures.append("dialogue normalization should keep narration lines without a speaker") @@ -4161,10 +4167,10 @@ func _check_ancient_ui_theme(failures: Array[String]) -> void: failures.append("post-move command header should show a generated move icon") elif post_move_header_icon.texture.get_width() < 128 or post_move_header_icon.texture.get_height() < 128: failures.append("post-move command header should use high-resolution generated move icon art") - _check_local_command_icon_button(failures, scene.post_move_attack_button, "공격", "attack") - _check_local_command_icon_button(failures, scene.post_move_tactic_button, "책략", "tactic") - _check_local_command_icon_button(failures, scene.post_move_item_button, "도구", "item") - _check_local_command_icon_button(failures, scene.post_move_wait_button, "대기", "wait") + _check_local_command_labeled_button(failures, scene.post_move_attack_button, "공격", "attack") + _check_local_command_labeled_button(failures, scene.post_move_tactic_button, "책략", "tactic") + _check_local_command_labeled_button(failures, scene.post_move_item_button, "도구", "item") + _check_local_command_labeled_button(failures, scene.post_move_wait_button, "대기", "wait") _check_picker_utility_button(failures, scene.post_move_cancel_button, "이동 취소", "cancel", BattleSceneScript.LOCAL_COMMAND_CANCEL_BUTTON_SIZE) if scene.post_move_picker_back_button == null or scene.post_move_picker_back_button.text != "목록 닫기": failures.append("post-move picker back button should use immediately readable wording") @@ -4917,6 +4923,24 @@ func _check_local_command_icon_button(failures: Array[String], button: Button, e failures.append("local command should reserve stable icon space: %s" % expected_label) +func _check_local_command_labeled_button(failures: Array[String], button: Button, expected_label: String, expected_icon: String) -> void: + if button == null: + failures.append("local command button missing: %s" % expected_label) + return + if bool(button.get_meta("icon_only", false)) or button.text != expected_label: + failures.append("local command should show readable Korean text after movement: %s text=%s" % [expected_label, button.text]) + if button.icon == null or str(button.get_meta("command_icon", "")) != expected_icon: + failures.append("local command should carry an immediate visual icon: %s icon=%s meta=%s" % [expected_label, str(button.icon), str(button.get_meta("command_icon", ""))]) + _check_button_uses_generated_toolbar_icon(failures, button, expected_label) + _check_button_uses_generated_surface(failures, button, expected_label) + if str(button.get_meta("command_label", "")) != expected_label: + failures.append("local command should keep old wording in hover metadata: %s meta=%s" % [expected_label, str(button.get_meta("command_label", ""))]) + if str(button.tooltip_text).strip_edges().is_empty() or not str(button.tooltip_text).contains(expected_label): + failures.append("local command should expose its function through hover tooltip: %s tooltip=%s" % [expected_label, str(button.tooltip_text)]) + if button.custom_minimum_size.x < BattleSceneScript.LOCAL_COMMAND_BUTTON_SIZE.x or button.custom_minimum_size.y < BattleSceneScript.LOCAL_COMMAND_BUTTON_SIZE.y: + failures.append("local command should reserve stable readable space: %s size=%s" % [expected_label, str(button.custom_minimum_size)]) + + func _check_picker_utility_button(failures: Array[String], button: Button, expected_label: String, expected_icon: String, minimum_size: Vector2) -> void: if button == null: failures.append("picker utility button missing: %s" % expected_label) @@ -5783,7 +5807,7 @@ func _check_terrain_and_unit_presentation(failures: Array[String]) -> void: failures.append("fallback terrain fill should be stronger than background fill") var background_texture_modulate: Color = scene._terrain_texture_modulate(Vector2i(0, 0), "G", true) var fallback_texture_modulate: Color = scene._terrain_texture_modulate(Vector2i(0, 0), "G", false) - if background_texture_modulate.a < 0.18: + if background_texture_modulate.a < 0.09: failures.append("background terrain texture should still leave a visible terrain hint over high-res art: %.3f" % background_texture_modulate.a) if background_texture_modulate.a >= fallback_texture_modulate.a: failures.append("background terrain texture should stay lighter than fallback terrain texture")