From b9ffd7553b875f92065bcc6b7cf94cbb15c0902d Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 28 Jun 2026 17:44:59 -0400 Subject: [PATCH] NEO-153: fix gdlint pre-push for contract HUD wiring. Extract contract HUD main wiring, dedupe deny copy, trim main.gd, and add wiring tests. --- client/scripts/contract_hud_controller.gd | 48 +++++----- client/scripts/contract_hud_main_wiring.gd | 43 +++++++++ .../scripts/contract_hud_main_wiring.gd.uid | 1 + client/scripts/interactable_world_builder.gd | 7 ++ client/scripts/main.gd | 95 ++++++------------- client/test/contract_hud_main_wiring_test.gd | 27 ++++++ .../test/contract_hud_main_wiring_test.gd.uid | 1 + 7 files changed, 132 insertions(+), 90 deletions(-) create mode 100644 client/scripts/contract_hud_main_wiring.gd create mode 100644 client/scripts/contract_hud_main_wiring.gd.uid create mode 100644 client/test/contract_hud_main_wiring_test.gd create mode 100644 client/test/contract_hud_main_wiring_test.gd.uid diff --git a/client/scripts/contract_hud_controller.gd b/client/scripts/contract_hud_controller.gd index 76655df..89f3c74 100644 --- a/client/scripts/contract_hud_controller.gd +++ b/client/scripts/contract_hud_controller.gd @@ -10,6 +10,13 @@ const PROTOTYPE_CONTRACT_SEED_BUCKET := "prototype_contract_dev_seed" const ISSUE_IDLE_HINT := "Contract issue: — (Shift+C prototype)" const ISSUE_SENDING_HINT := "Contract issue: sending…" const REWARD_HEADER := "Contract rewards:" +const ISSUE_DENY_COPY := { + "active_contract_exists": "Contract issue: denied — active contract already issued", + "no_eligible_template": "Contract issue: denied — no eligible template (standing/band)", + "economy_cap_exceeded": "Contract issue: denied — economy cap exceeded", + "invalid_reward_bundle": "Contract issue: denied — invalid reward bundle", + "unknown_template": "Contract issue: denied — unknown template", +} var _contract_client: Node = null var _item_defs_client: Node = null @@ -84,13 +91,14 @@ func try_issue_key_input(event: InputEvent) -> bool: if not event is InputEventKey: return false var k := event as InputEventKey - if not k.pressed or k.echo or not k.shift_pressed: - return false - if k.physical_keycode != KEY_C and k.keycode != KEY_C: - return false - if not is_instance_valid(_contract_client): - return false - if not _contract_client.has_method("request_issue"): + if ( + not k.pressed + or k.echo + or not k.shift_pressed + or (k.physical_keycode != KEY_C and k.keycode != KEY_C) + or not is_instance_valid(_contract_client) + or not _contract_client.has_method("request_issue") + ): return false if bool(_contract_client.call("is_issue_busy")): return true @@ -274,21 +282,13 @@ func _render_reward_label() -> void: func _format_issue_deny(reason_code: String) -> String: - match reason_code: - "active_contract_exists": - return "Contract issue: denied — active contract already issued" - "no_eligible_template": - return "Contract issue: denied — no eligible template (standing/band)" - "economy_cap_exceeded": - return "Contract issue: denied — economy cap exceeded" - "invalid_reward_bundle": - return "Contract issue: denied — invalid reward bundle" - "unknown_template": - return "Contract issue: denied — unknown template" - "": - return "Contract issue: denied (no reasonCode)" - _: - return "Contract issue: denied — %s" % reason_code + var rc := reason_code.strip_edges() + if rc.is_empty(): + return "Contract issue: denied (no reasonCode)" + var copy_variant: Variant = ISSUE_DENY_COPY.get(rc, null) + if copy_variant is String: + return copy_variant as String + return "Contract issue: denied — %s" % rc func _format_reward_summary_lines(summary: Dictionary) -> PackedStringArray: @@ -325,7 +325,9 @@ func _format_reward_summary_lines(summary: Dictionary) -> PackedStringArray: var amount: int = int(grant.get("amount", 0)) if faction_id.is_empty() or amount <= 0: continue - lines.append(" %s +%d rep" % [FactionStandingClient.display_name_for(faction_id), amount]) + lines.append( + " %s +%d rep" % [FactionStandingClient.display_name_for(faction_id), amount] + ) if lines.is_empty(): lines.append(" (no grants)") return lines diff --git a/client/scripts/contract_hud_main_wiring.gd b/client/scripts/contract_hud_main_wiring.gd new file mode 100644 index 0000000..d90132d --- /dev/null +++ b/client/scripts/contract_hud_main_wiring.gd @@ -0,0 +1,43 @@ +extends RefCounted + +## NEO-153: thin main.gd wiring (keeps main.gd under gdlint max-file-lines). + +const ContractHudController := preload("res://scripts/contract_hud_controller.gd") + + +static func setup_on( + main: Node, + hud_root: Node, + contract_client: Node, + apply_http_config: Callable, + item_defs_client: Node = null, + inventory_client: Node = null, + skill_progression_client: Node = null, + faction_standing_client: Node = null +) -> Node: + var controller: Node = ContractHudController.new() + main.add_child(controller) + controller.call( + "setup", + contract_client, + hud_root.get_node("ContractActiveLabel"), + hud_root.get_node("ContractIssueFeedbackLabel"), + hud_root.get_node("ContractRewardDeliveryLabel"), + apply_http_config, + item_defs_client, + inventory_client, + skill_progression_client, + faction_standing_client + ) + return controller + + +static func request_refresh(controller: Node) -> void: + if is_instance_valid(controller): + controller.call("request_contract_refresh") + + +static func try_issue_key_input(controller: Node, event: InputEvent) -> bool: + if not is_instance_valid(controller): + return false + return bool(controller.call("try_issue_key_input", event)) diff --git a/client/scripts/contract_hud_main_wiring.gd.uid b/client/scripts/contract_hud_main_wiring.gd.uid new file mode 100644 index 0000000..43ced63 --- /dev/null +++ b/client/scripts/contract_hud_main_wiring.gd.uid @@ -0,0 +1 @@ +uid://bneo153mainwire01 diff --git a/client/scripts/interactable_world_builder.gd b/client/scripts/interactable_world_builder.gd index fe94a30..0f8d5fe 100644 --- a/client/scripts/interactable_world_builder.gd +++ b/client/scripts/interactable_world_builder.gd @@ -120,3 +120,10 @@ static func _add_prop_visual( prop_mat.albedo_color = Color(0.28, 0.38, 0.45, 1.0) prop_mesh.material_override = prop_mat body.add_child(prop_mesh) + + +static func set_collision_shapes_disabled(root: Node, disabled: bool) -> void: + if root is CollisionShape3D: + (root as CollisionShape3D).disabled = disabled + for c in root.get_children(): + set_collision_shapes_disabled(c, disabled) diff --git a/client/scripts/main.gd b/client/scripts/main.gd index f542edb..8397b3b 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -57,7 +57,8 @@ const BREACH_GIG_ID := "breach" const PROTOTYPE_ENCOUNTER_ID := "prototype_combat_pocket" const PROTOTYPE_ENCOUNTER_REQUIRED_TARGETS := 3 const QuestHudController := preload("res://scripts/quest_hud_controller.gd") -const ContractHudController := preload("res://scripts/contract_hud_controller.gd") +const ContractHudMainWiring := preload("res://scripts/contract_hud_main_wiring.gd") +const InteractableWorldBuilder := preload("res://scripts/interactable_world_builder.gd") ## Bump on each rejection so older one-shot timers do not clear a newer message. var _move_reject_msg_token: int = 0 @@ -139,9 +140,6 @@ var _dev_pulse_bootstrap_attempted: bool = false @onready var _quest_progress_label: Label = _hud_root.get_node("QuestProgressLabel") @onready var _quest_accept_feedback_label: Label = _hud_root.get_node("QuestAcceptFeedbackLabel") @onready var _quest_reward_delivery_label: Label = _hud_root.get_node("QuestRewardDeliveryLabel") -@onready var _contract_active_label: Label = _hud_root.get_node("ContractActiveLabel") -@onready var _contract_issue_feedback_label: Label = _hud_root.get_node("ContractIssueFeedbackLabel") -@onready var _contract_reward_delivery_label: Label = _hud_root.get_node("ContractRewardDeliveryLabel") @onready var _faction_standing_label: Label = _hud_root.get_node("FactionStandingLabel") @onready var _npc_state_label: Label = _hud_root.get_node("NpcStateLabel") @onready var _telegraph_label: Label = _hud_root.get_node("TelegraphLabel") @@ -213,7 +211,16 @@ func _ready() -> void: _setup_gig_progression_sync() _setup_encounter_progress_sync() _setup_quest_progress_sync() - _setup_contract_hud() + _contract_hud = ContractHudMainWiring.setup_on( + self, + _hud_root, + _contract_client, + Callable(self, "_apply_authority_http_config_to_client"), + _item_defs_client, + _inventory_client, + _skill_progression_client, + _faction_standing_client + ) _setup_gather_interact_feedback() _setup_craft_ui() @@ -756,7 +763,7 @@ func _on_encounter_progress_received(snapshot: Dictionary) -> void: if str(row.get("state", "")) == "completed": _request_inventory_refresh() _request_quest_progress_refresh() - _request_contract_refresh() + ContractHudMainWiring.request_refresh(_contract_hud) func _on_encounter_progress_sync_failed(reason: String) -> void: @@ -893,35 +900,6 @@ func _try_quest_accept_key_input(event: InputEvent) -> bool: return false -func _setup_contract_hud() -> void: - # NEO-153: contract issue + progress HUD; boot hydrate + refresh after issue/encounter clear. - _contract_hud = ContractHudController.new() - add_child(_contract_hud) - _contract_hud.call( - "setup", - _contract_client, - _contract_active_label, - _contract_issue_feedback_label, - _contract_reward_delivery_label, - Callable(self, "_apply_authority_http_config_to_client"), - _item_defs_client, - _inventory_client, - _skill_progression_client, - _faction_standing_client - ) - - -func _request_contract_refresh() -> void: - if is_instance_valid(_contract_hud): - _contract_hud.call("request_contract_refresh") - - -func _try_contract_issue_key_input(event: InputEvent) -> bool: - if is_instance_valid(_contract_hud): - return bool(_contract_hud.call("try_issue_key_input", event)) - return false - - func _render_gather_feedback_label(text: String = "Gather: —") -> void: if is_instance_valid(_gather_feedback_label): _gather_feedback_label.text = text @@ -1374,27 +1352,19 @@ func _render_player_combat_hp_label() -> void: func _render_npc_combat_hud_labels() -> void: var rows: Dictionary = _resolve_hud_npc_rows() - _render_npc_state_label(rows) - _render_telegraph_label(rows) + if is_instance_valid(_npc_state_label): + _npc_state_label.text = NpcCombatHudHelpers.build_npc_state_label( + rows, _npc_runtime_sync_error + ) + if is_instance_valid(_telegraph_label): + _telegraph_label.text = NpcCombatHudHelpers.build_telegraph_label( + rows, _npc_runtime_hud_state, _npc_runtime_sync_error + ) _npc_combat_hud_needs_tick = NpcCombatHudHelpers.has_interpolated_telegraph_display( rows, _npc_runtime_hud_state ) -func _render_npc_state_label(rows: Dictionary) -> void: - if not is_instance_valid(_npc_state_label): - return - _npc_state_label.text = NpcCombatHudHelpers.build_npc_state_label(rows, _npc_runtime_sync_error) - - -func _render_telegraph_label(rows: Dictionary) -> void: - if not is_instance_valid(_telegraph_label): - return - _telegraph_label.text = NpcCombatHudHelpers.build_telegraph_label( - rows, _npc_runtime_hud_state, _npc_runtime_sync_error - ) - - func _on_move_rejected(reason_code: String) -> void: _authority_force_snap_next = true # Rejected stream: server state may differ; next snap is forced. @@ -1444,7 +1414,7 @@ func _try_route_gameplay_key_input(event: InputEvent) -> bool: return true if _try_quest_accept_key_input(event): return true - if _try_contract_issue_key_input(event): + if ContractHudMainWiring.try_issue_key_input(_contract_hud, event): return true if _try_inventory_refresh_input(event): return true @@ -1580,7 +1550,7 @@ func _dev_toggle_obstacle_smoke_deferred() -> void: _dev_saved_obstacle_mask = co.collision_mask co.collision_layer = 0 co.collision_mask = 0 - _dev_collision_shapes_set_disabled(obstacle, true) + InteractableWorldBuilder.set_collision_shapes_disabled(obstacle, true) obstacle.process_mode = Node.PROCESS_MODE_DISABLED obstacle.visible = false if obstacle.get_parent() == _nav_region: @@ -1594,7 +1564,7 @@ func _dev_toggle_obstacle_smoke_deferred() -> void: if co2 != null and _dev_saved_obstacle_layer >= 0: co2.collision_layer = _dev_saved_obstacle_layer co2.collision_mask = _dev_saved_obstacle_mask - _dev_collision_shapes_set_disabled(obstacle, false) + InteractableWorldBuilder.set_collision_shapes_disabled(obstacle, false) call_deferred("_dev_rebake_nav_after_obstacle_toggle_deferred") @@ -1610,28 +1580,19 @@ func _dev_rebake_nav_after_obstacle_toggle_deferred() -> void: _player.sync_navigation_agent_after_map_rebuild(_nav_region) -func _dev_collision_shapes_set_disabled(root: Node, disabled: bool) -> void: - if root is CollisionShape3D: - (root as CollisionShape3D).disabled = disabled - for c in root.get_children(): - _dev_collision_shapes_set_disabled(c, disabled) - - func _on_interactables_catalog_ready(descriptors: Array) -> void: _interactables_catalog = descriptors.duplicate(true) - var groups: Variant = load("res://scripts/interactable_world_builder.gd").call( - "build_from_catalog", descriptors, _interactables_root, _radius_preview + var groups: Variant = InteractableWorldBuilder.build_from_catalog( + descriptors, _interactables_root, _radius_preview ) if _radius_preview.has_method("setup_glow_groups"): _radius_preview.call("setup_glow_groups", groups as Array) func _on_interactables_catalog_failed(reason: String) -> void: - # `InteractablesCatalogClient` already `push_error`s; this hook exists for wiring clarity. - var readme := "Interaction + range preview (NEO-9 + NEO-25)" push_warning( ( - "Interactables catalog failed (%s) — start the game server first; see client README (%s)." - % [reason, readme] + "Interactables catalog failed (%s) — start the game server first; see client README." + % reason ) ) diff --git a/client/test/contract_hud_main_wiring_test.gd b/client/test/contract_hud_main_wiring_test.gd new file mode 100644 index 0000000..4d5e29e --- /dev/null +++ b/client/test/contract_hud_main_wiring_test.gd @@ -0,0 +1,27 @@ +extends GdUnitTestSuite + +## NEO-153: thin main wiring helpers. + +const ContractHudMainWiring := preload("res://scripts/contract_hud_main_wiring.gd") +const InteractableWorldBuilder := preload("res://scripts/interactable_world_builder.gd") + + +func test_try_issue_key_input_returns_false_without_controller() -> void: + # Arrange + var event := InputEventKey.new() + # Act + var handled: bool = ContractHudMainWiring.try_issue_key_input(null, event) + # Assert + assert_bool(handled).is_false() + + +func test_set_collision_shapes_disabled_recurses_tree() -> void: + # Arrange + var root := Node3D.new() + var shape := CollisionShape3D.new() + root.add_child(shape) + # Act + InteractableWorldBuilder.set_collision_shapes_disabled(root, true) + # Assert + assert_bool(shape.disabled).is_true() + root.free() diff --git a/client/test/contract_hud_main_wiring_test.gd.uid b/client/test/contract_hud_main_wiring_test.gd.uid new file mode 100644 index 0000000..0df6e6e --- /dev/null +++ b/client/test/contract_hud_main_wiring_test.gd.uid @@ -0,0 +1 @@ +uid://bneo153mwtest01