From 5326b5844d15b1500a1593d389e3a44f5e0e9682 Mon Sep 17 00:00:00 2001 From: Shane Wall Date: Thu, 17 Sep 2026 09:18:01 +1000 Subject: [PATCH 1/3] Delete the 23 plugin.gd delegates nothing called The viewport drop path was a duplicate: plugin_drop_handler.gd is the live one, reached through the two Godot virtuals, and it dispatches the four payload kinds itself. The eight wrappers in front of that dispatch had no caller. Four architecture tests were keeping these alive. Each enumerates module methods and asserts plugin.gd contains a delegation string for every one, and the lists had grown to include helpers the modules call themselves: update_preview, apply_value, do_displacement_stroke, point_near_polygon_3d, update_prefab_hover, move_selected_vertical. The only way to satisfy that was a wrapper nothing called. The lists are now the entry points plugin.gd actually owns. The drop test also asserts plugin.gd does not classify the payloads itself, which is the property "thin delegate" was reaching for and the old list did not check. --- addons/hammerforge/plugin.gd | 98 --------------------------- tests/test_plugin_drop_handler.gd | 35 ++++++---- tests/test_plugin_edit_actions.gd | 5 +- tests/test_plugin_numeric_input.gd | 18 +++-- tests/test_plugin_pointer_handlers.gd | 8 ++- 5 files changed, 44 insertions(+), 120 deletions(-) diff --git a/addons/hammerforge/plugin.gd b/addons/hammerforge/plugin.gd index 381acfe4..32e1753c 100644 --- a/addons/hammerforge/plugin.gd +++ b/addons/hammerforge/plugin.gd @@ -783,16 +783,6 @@ func _on_paint_connector_confirm_requested() -> void: _confirm_floor_paint_connector(root) -func _do_disp_paint_stroke(root: Node, cam: Camera3D, pos: Vector2) -> void: - HFPluginPaintInput.do_displacement_stroke(self, root, cam, pos) - - -func _point_near_polygon_3d( - point: Vector3, verts: PackedVector3Array, normal: Vector3, margin: float -) -> bool: - return HFPluginPaintInput.point_near_polygon_3d(point, verts, normal, margin) - - func _handle_paint_input(event: InputEvent, root: Node, cam: Camera3D, pos: Vector2) -> int: return HFPluginPaintInput.handle_paint(self, event, root, cam, pos) @@ -818,14 +808,6 @@ func _handle_numeric_input(event: InputEventKey, root: Node) -> int: return HFPluginNumericInput.handle(self, event, root) -func _update_numeric_preview(root: Node) -> void: - HFPluginNumericInput.update_preview(self, root) - - -func _apply_numeric_value(root: Node) -> void: - HFPluginNumericInput.apply_value(self, root) - - func _cancel_selection_gesture() -> bool: var gesture = _ensure_selection_runtime_state() var was_active: bool = bool(gesture.is_active()) @@ -1070,10 +1052,6 @@ func _handle_mouse_motion( return HFPluginPointerTools.handle_motion(self, event, root, cam, pos, tool_id) -func _update_prefab_hover_overlay(root, cam: Camera3D, pos: Vector2) -> void: - HFPluginPointerTools.update_prefab_hover(root, cam, pos) - - # --------------------------------------------------------------------------- # Vertex editing mode # --------------------------------------------------------------------------- @@ -1111,10 +1089,6 @@ func _update_vertex_overlay(root: Node, _cam: Camera3D) -> void: HFPluginOverlays.update_vertex_overlay(self, root) -func _ensure_vertex_overlay(root: Node) -> void: - HFPluginOverlays.ensure_vertex_overlay(self, root) - - func _clear_vertex_overlay() -> void: HFPluginOverlays.clear_vertex_overlay(self) @@ -1148,10 +1122,6 @@ func _finalize_native_selection(selection_before: Array, additive: bool, toggle: HFPluginSelectionState.finalize_native_selection(self, selection_before, additive, toggle) -func _normalize_editor_selection(nodes: Array, root: Node) -> Array: - return HFPluginSelectionState.normalize_editor_selection(self, nodes, root) - - func _hammerforge_selection_owner(node: Node, root: Node) -> Node: return HFPluginSelectionState.normalize_managed_selection_owner(node, root) @@ -1160,14 +1130,6 @@ static func normalize_managed_selection_owner(node: Node, root: Node) -> Node: return HFPluginSelectionState.normalize_managed_selection_owner(node, root) -func _expand_native_group_selection( - root: Node, selection_before: Array, current_selection: Array, toggle: bool -) -> Array: - return HFPluginSelectionState.expand_native_group_selection( - root, selection_before, current_selection, toggle - ) - - static func expand_native_group_members( selection_before: Array, current_selection: Array, toggle: bool, groups: Dictionary ) -> Array: @@ -1176,10 +1138,6 @@ static func expand_native_group_members( ) -static func _same_node_selection(first: Array, second: Array) -> bool: - return HFPluginSelectionState.same_node_selection(first, second) - - func _apply_selection_list(nodes: Array, additive: bool, toggle: bool = false) -> void: HFPluginSelectionState.apply_selection_list(self, nodes, additive, toggle) @@ -1188,18 +1146,6 @@ func _apply_hf_selection(selection: EditorSelection) -> void: HFPluginSelectionState.apply_hf_selection(self, selection) -func _sync_hf_selection_if_empty() -> void: - HFPluginSelectionState.sync_hf_selection_if_empty(self) - - -func _selection_has_brush(nodes: Array, root: Node) -> bool: - return HFPluginSelectionState.selection_has_brush(nodes, root) - - -func _selection_has_entity(nodes: Array, root: Node) -> bool: - return HFPluginSelectionState.selection_has_entity(nodes, root) - - static func classify_selection_scope(nodes: Array, root: Node) -> int: return HFPluginSelectionState.classify_selection_scope(nodes, root) @@ -1284,10 +1230,6 @@ func _select_faces_in_rect( HFPluginSelectionInput.select_faces_in_rect(self, root, camera, from, to, additive, toggle) -func _face_screen_center(camera: Camera3D, brush: DraftBrush, face) -> Vector2: - return HFPluginSelectionInput.face_screen_center(camera, brush, face) - - func _face_key_for(brush: DraftBrush) -> String: return HFPluginSelectionCommands.face_key_for(brush) @@ -1437,10 +1379,6 @@ func _move_selected_to_ceiling(root: Node) -> bool: return HFPluginEditActions.move_selected_to_ceiling(self, root) -func _move_selected_vertical(root: Node, action_name: String, method_name: String) -> bool: - return HFPluginEditActions.move_selected_vertical(self, root, action_name, method_name) - - func _clip_selected(root: Node) -> bool: return HFPluginEditActions.clip_selected(self, root) @@ -1461,30 +1399,6 @@ func _drop_data(position: Vector2, data: Variant) -> void: HFPluginDropHandler.drop_data(self, position, data) -func _is_entity_drag_data(data: Variant) -> bool: - return HFPluginDropHandler.is_entity_drag_data(data) - - -func _handle_entity_drop(position: Vector2, data: Variant) -> void: - HFPluginDropHandler.handle_entity_drop(self, position, data) - - -func _is_brush_preset_drag_data(data: Variant) -> bool: - return HFPluginDropHandler.is_brush_preset_drag_data(data) - - -func _handle_brush_preset_drop(position: Vector2, data: Variant) -> void: - HFPluginDropHandler.handle_brush_preset_drop(self, position, data) - - -func _is_prefab_drag_data(data: Variant) -> bool: - return HFPluginDropHandler.is_prefab_drag_data(data) - - -func _handle_prefab_drop(position: Vector2, data: Variant) -> void: - HFPluginDropHandler.handle_prefab_drop(self, position, data) - - # --------------------------------------------------------------------------- # Prefab enhancement helpers # --------------------------------------------------------------------------- @@ -1506,14 +1420,6 @@ func _propagate_prefab(root) -> void: HFPluginPrefabCommands.propagate(self, root) -func _is_material_drag_data(data: Variant) -> bool: - return HFPluginDropHandler.is_material_drag_data(data) - - -func _handle_material_drop(position: Vector2, data: Variant) -> void: - HFPluginDropHandler.handle_material_drop(self, position, data) - - # --------------------------------------------------------------------------- # Context Toolbar + Hotkey Palette handlers # --------------------------------------------------------------------------- @@ -1606,10 +1512,6 @@ func _handle_double_tap(keycode: int, root: Node, paint_mode: bool) -> bool: return HFPluginOverlays.handle_double_tap(self, keycode, root, paint_mode) -func _show_quick_property_at_cursor(prop_type: int, values: Array) -> void: - HFPluginOverlays.show_quick_property(self, prop_type, values) - - func _on_quick_property_committed(property_type: int, values: Array) -> void: HFPluginOverlays.on_quick_property_committed(self, property_type, values) diff --git a/tests/test_plugin_drop_handler.gd b/tests/test_plugin_drop_handler.gd index 8b24dd5d..8ffbc1ab 100644 --- a/tests/test_plugin_drop_handler.gd +++ b/tests/test_plugin_drop_handler.gd @@ -14,20 +14,31 @@ func test_drop_data_classification_accepts_only_supported_payloads() -> void: func test_plugin_drop_callbacks_are_thin_delegates() -> void: + # Godot calls `_can_drop_data` and `_drop_data` on the plugin, and those two + # are the whole of the viewport drop path it owns. `drop_data()` dispatches to + # the four payload kinds itself, so plugin.gd had a second layer of wrappers + # in front of that dispatch which nothing called (#609). + # + # The delegation is asserted on the two entry points that exist, and the + # absence of the classification is asserted directly. Listing the wrappers was + # the weaker check: it passed while plugin.gd held a duplicate of the payload + # logic, which is the thing "thin delegate" is supposed to rule out. var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") - for method_name in [ - "can_drop_data", - "drop_data", - "is_entity_drag_data", - "handle_entity_drop", - "is_brush_preset_drag_data", - "handle_brush_preset_drop", - "is_prefab_drag_data", - "handle_prefab_drop", - "is_material_drag_data", - "handle_material_drop", + for method_name in ["can_drop_data", "drop_data"]: + assert_true( + source.contains("HFPluginDropHandler.%s" % method_name), + "plugin.gd hands %s straight to the drop handler" % method_name + ) + for payload in [ + "hammerforge_entity", + "hammerforge_brush_preset", + "hammerforge_prefab", + "hammerforge_material", ]: - assert_true(source.contains("HFPluginDropHandler.%s" % method_name)) + assert_false( + source.contains(payload), + "plugin.gd does not classify %s itself; the drop handler does" % payload + ) func test_drop_handler_keeps_placement_selection_and_undo_contracts() -> void: diff --git a/tests/test_plugin_edit_actions.gd b/tests/test_plugin_edit_actions.gd index 0a45bf56..a2565a76 100644 --- a/tests/test_plugin_edit_actions.gd +++ b/tests/test_plugin_edit_actions.gd @@ -2,6 +2,10 @@ extends GutTest func test_plugin_managed_edit_callbacks_are_thin_delegates() -> void: + # The actions plugin.gd offers. `move_selected_vertical()` is not one of them: + # it is the helper `move_selected_to_floor()` and `move_selected_to_ceiling()` + # share inside the module, and plugin.gd used to carry a wrapper for it that + # nothing called, which this list is why (#609). var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") for method_name in [ "delete_selected", @@ -13,7 +17,6 @@ func test_plugin_managed_edit_callbacks_are_thin_delegates() -> void: "merge_selected", "move_selected_to_floor", "move_selected_to_ceiling", - "move_selected_vertical", "clip_selected", "carve_selected", ]: diff --git a/tests/test_plugin_numeric_input.gd b/tests/test_plugin_numeric_input.gd index 6e978042..2488e68f 100644 --- a/tests/test_plugin_numeric_input.gd +++ b/tests/test_plugin_numeric_input.gd @@ -191,13 +191,19 @@ func test_enter_commits_numeric_draw_height() -> void: func test_plugin_callbacks_are_thin_numeric_delegates() -> void: + # `handle()` is the entry point, and it is the only one plugin.gd has any + # reason to reach: `update_preview()` and `apply_value()` are called by + # `handle()` itself. plugin.gd used to carry a wrapper for each of them that + # nothing called, and this assertion is why they survived (#609). What the + # module does with them is asserted above, against the module. var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") - for call in [ - "HFPluginNumericInput.handle", - "HFPluginNumericInput.update_preview", - "HFPluginNumericInput.apply_value", - ]: - assert_true(source.contains(call), "%s must be delegated" % call) + assert_true( + source.contains("HFPluginNumericInput.handle"), + "plugin.gd hands numeric input straight to the module" + ) + assert_false( + source.contains("numeric_buffer.is_valid_float()"), "and does not parse the buffer itself" + ) func test_keypad_digits_and_decimal_fill_the_buffer() -> void: diff --git a/tests/test_plugin_pointer_handlers.gd b/tests/test_plugin_pointer_handlers.gd index 70148982..eda81a74 100644 --- a/tests/test_plugin_pointer_handlers.gd +++ b/tests/test_plugin_pointer_handlers.gd @@ -24,17 +24,19 @@ func test_polygon_margin_accepts_inside_and_rejects_distant_points() -> void: func test_plugin_pointer_callbacks_are_thin_delegates() -> void: var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") + # The entry points plugin.gd owns. `do_displacement_stroke()`, + # `point_near_polygon_3d()` and `update_prefab_hover()` are called by the + # handlers below them, inside their own modules; plugin.gd used to carry a + # wrapper for each that nothing called, and this assertion is why they + # survived (#609). for call in [ "HFPluginPaintInput.should_start_displacement", "HFPluginPaintInput.handle_displacement", "HFPluginPaintInput.commit_displacement_undo", - "HFPluginPaintInput.do_displacement_stroke", - "HFPluginPaintInput.point_near_polygon_3d", "HFPluginPaintInput.handle_paint", "HFPluginPointerTools.handle_extrude", "HFPluginPointerTools.handle_draw", "HFPluginPointerTools.handle_motion", - "HFPluginPointerTools.update_prefab_hover", ]: assert_true(source.contains(call), "%s must be delegated" % call) From c392921238404e57e932e09f4c2369c465a6bc7d Mon Sep 17 00:00:00 2001 From: Shane Wall Date: Thu, 17 Sep 2026 09:32:48 +1000 Subject: [PATCH 2/3] Delete the dead declarations in the rest of the tree Twenty in dock.gd, eight in level_root.gd, and seven elsewhere, plus the nine fields from #610. map_io.gd's _format_face_line was a drifted second copy of the .map face line. It takes three arguments and hard-codes the texture and the 0 0 0 1 1 tail; the three adapters take (a, b, c, texture, face_data) and read real UV data off FaceData. Nothing could have called it even by accident. drag_active was worse than unused. Its setter called input_state.cancel() while cancel_drag() is input_state.cancel() and _clear_preview(), so setting it false did half a cancel and orphaned the preview brush. It is public on a registered custom type, so anyone who found it got the broken half. grid_plane_axis and _entity_props_entity were written and never read, so the writes went with the declarations. selection_has_brush and selection_has_entity became provably dead once their only caller, a dead plugin.gd wrapper, was gone. Rerunning the scan afterwards finds no third layer: zero functions, zero fields. --- CHANGELOG.md | 48 ++++++++++ addons/hammerforge/brush_instance.gd | 8 -- addons/hammerforge/dock.gd | 88 ------------------- addons/hammerforge/dock_entity_handler.gd | 2 - addons/hammerforge/draft_entity.gd | 6 -- addons/hammerforge/level_root.gd | 57 ------------ addons/hammerforge/map_io.gd | 7 -- addons/hammerforge/plugin_selection_state.gd | 18 ---- addons/hammerforge/systems/hf_drag_system.gd | 6 -- addons/hammerforge/systems/hf_grid_system.gd | 1 - .../systems/hf_subtract_preview.gd | 8 -- addons/hammerforge/ui/hf_context_toolbar.gd | 3 - addons/hammerforge/ui/hf_hotkey_palette.gd | 5 -- addons/hammerforge/ui/hf_io_wiring_panel.gd | 1 - tests/test_core_loop_overlays.gd | 2 - tests/test_selection_gesture.gd | 10 +-- 16 files changed, 51 insertions(+), 219 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3453e3fc..62395f8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,54 @@ The format is based on Keep a Changelog, and this project follows semantic versi has not been verified, and deleting them would lose that while making the document look healthier. +### Removed +- **Fifty-seven private functions and nine fields that nothing used** (#609, + #610). `plugin.gd` and `level_root.gd` are documented as thin coordinators + whose named one-line delegates exist to be called by name, and the rule beside + that is that a delegate with no caller gets deleted. These had drifted past it. + The whole viewport drop path in `plugin.gd` was a second layer: Godot calls + `_can_drop_data` and `_drop_data`, those hand straight to + `plugin_drop_handler.gd`, and its own `drop_data()` dispatches the four payload + kinds itself, so the eight wrappers in front of that dispatch were reachable + from nothing. + + Five architecture tests were keeping much of this alive, which is the more + useful half of the finding. Each enumerates a module's methods and asserts + `plugin.gd` contains a delegation string for every one, and the lists had grown + to include helpers the modules call themselves: `update_preview`, + `apply_value`, `do_displacement_stroke`, `point_near_polygon_3d`, + `update_prefab_hover`, `move_selected_vertical`, `normalize_editor_selection`, + `expand_native_group_selection`, `same_node_selection`, + `sync_hf_selection_if_empty`, `face_screen_center`, `show_quick_property`, + `ensure_vertex_overlay`. The only way to satisfy the assertion was a wrapper + nothing called, so tests written to enforce that `plugin.gd` stays thin were + requiring it to be thicker. Each list is now the entry points `plugin.gd` + actually owns, and the drop test additionally asserts that `plugin.gd` does not + contain the payload type strings, which is the property "thin delegate" was + reaching for and the enumeration never checked. + + `HFPluginSelectionState.selection_has_brush()` and `selection_has_entity()` went + too. Their only caller was the dead wrapper, so removing it made them provably + dead in the same pass. + + Two of the fields are public on registered custom types, so both are a break + for any script that touched them, and both earn it. `DraftEntity.entity_properties` + was a second name for `entity_data` that nothing in the plugin used, and two + names for one dictionary can only ever diverge. `LevelRoot.drag_active` is the + worse of the two: its setter called `input_state.cancel()` while the canonical + `cancel_drag()` is `input_state.cancel()` **and** `_clear_preview()`, so setting + `drag_active = false` did half a cancel and left the preview brush orphaned in + the scene. It was a latent bug rather than dead weight. `LevelRoot.grid_plane_axis` + and `dock.gd`'s `_entity_props_entity` were written and never read, so the + writes went with the declarations. + + `map_io.gd`'s `_format_face_line()` was a drifted second copy of the `.map` + face line: it takes three arguments and hard-codes the texture and the + `0 0 0 1 1` tail, while the three adapters take `(a, b, c, texture, face_data)` + and read real UV data off `FaceData`. Nothing could have called it even by + accident, and a second copy of the export format is a trap for whoever fixes + the exporter next. + ### Fixed - **A visgroup can be renamed, a prefab variant deleted, a displacement's power changed** (#615). Three level-editing operations were implemented, carefully, diff --git a/addons/hammerforge/brush_instance.gd b/addons/hammerforge/brush_instance.gd index f18429ac..bda0036b 100644 --- a/addons/hammerforge/brush_instance.gd +++ b/addons/hammerforge/brush_instance.gd @@ -1509,13 +1509,5 @@ func _discard_private_visual(node: Node) -> void: # Compatibility entry points for editor code or third-party tools that called # the previous private helpers directly. -func _apply_brush_entity_overlay() -> void: - _sync_visual_overlays() - - -func _apply_subtract_wireframe_overlay() -> void: - _sync_visual_overlays() - - func _apply_additive_wireframe_overlay() -> void: _sync_visual_overlays() diff --git a/addons/hammerforge/dock.gd b/addons/hammerforge/dock.gd index 3aadc510..88c2f8a3 100644 --- a/addons/hammerforge/dock.gd +++ b/addons/hammerforge/dock.gd @@ -378,7 +378,6 @@ var active_shape: int = LevelRootType.BrushShape.BOX var shape_id_to_key: Dictionary = {} var paint_layers_signature: String = "" var materials_signature: String = "" -var surface_paint_signature: String = "" var root_properties: Dictionary = {} var history_entries: Array = [] var history_max := 50 @@ -549,8 +548,6 @@ var _io_wiring_section: VBoxContainer = null # Entity Properties controls var _entity_props_section: VBoxContainer = null var _entity_props_controls: Array = [] -var _entity_props_entity: Node3D = null - # Displacement / Bevel UI controls var _disp_section: HFCollapsibleSection = null var _disp_power_spin: SpinBox = null @@ -574,10 +571,6 @@ var _bevel_inset_dist_spin: SpinBox = null var _bevel_inset_height_spin: SpinBox = null -func _is_level_root(node: Node) -> bool: - return node != null and node is LevelRootType - - func _find_level_root_in(scene: Node) -> Node: if not scene: return null @@ -1074,15 +1067,6 @@ func _on_paint_mode_toggled(enabled: bool) -> void: builtin_tool_changed.emit() -func _on_welcome_dismissed(dont_show_again: bool) -> void: - if dont_show_again and _user_prefs: - _user_prefs.set_pref("show_welcome", false) - _user_prefs.save() - var tabs = $Margin/VBox/MainTabs - if tabs: - tabs.visible = true - - ## Persist a pref change to disk. func _save_user_pref(key: String, value: Variant) -> void: if not _user_prefs: @@ -1329,14 +1313,6 @@ func _find_editor_icon(icon_names: Array) -> Texture2D: return HFEditorTheme.find_editor_icon(editor_base_control, self, icon_names) -func _has_editor_icon(icon_name: String) -> bool: - return HFEditorTheme.has_editor_icon(editor_base_control, self, icon_name) - - -func _get_editor_icon(icon_name: String) -> Texture2D: - return HFEditorTheme.get_editor_icon(editor_base_control, self, icon_name) - - func _get_editor_color(color_name: String, fallback: Color) -> Color: return HFEditorTheme.get_editor_color(editor_base_control, self, color_name, fallback) @@ -1429,10 +1405,6 @@ func _build_paint_tab() -> void: builder.build(root_vbox) -func _build_entity_props_section() -> void: - pass # Now built by EntityTabBuilder - - func _rebuild_entity_props(entity: Node3D) -> void: HFDockEntityHandler.rebuild_entity_props(self, entity) @@ -1461,10 +1433,6 @@ func _can_edit_selected_entity(entity: Node3D) -> bool: return HFDockEntityHandler.can_edit_selected_entity(self, entity) -func _entity_prop_default(type_name: String, value: Variant) -> Variant: - return HFDockEntityHandler.entity_prop_default(type_name, value) - - # --------------------------------------------------------------------------- # External Tool Settings — auto-generated UI from HFEditorTool.get_settings_schema() # --------------------------------------------------------------------------- @@ -3194,10 +3162,6 @@ func _on_bake_dry_run() -> void: HFDockManageHandler.on_bake_dry_run(self) -func _get_bake_preview_mode() -> int: - return HFDockManageHandler.get_bake_preview_mode(self) - - func _on_bake_selected() -> void: await HFDockManageHandler.on_bake_selected(self) @@ -3210,10 +3174,6 @@ func _on_bake_check_issues() -> void: HFDockManageHandler.on_bake_check_issues(self) -func _update_bake_estimate() -> void: - HFDockManageHandler.update_bake_estimate(self) - - func _on_validate_level() -> void: HFDockManageHandler.on_validate_level(self) @@ -3404,14 +3364,6 @@ func _on_create_entity() -> void: HFDockEntityHandler.on_create_entity(self) -func _focus_entity_selection(entity: Node) -> void: - HFDockEntityHandler.focus_entity_selection(self, entity) - - -func _get_default_entity_definition() -> Dictionary: - return HFDockEntityHandler.get_default_entity_definition(self) - - func _connect_root_signals() -> void: HFDockConnections.connect_root(self) @@ -3616,30 +3568,10 @@ func _on_quick_play_selected_area() -> void: await HFDockManageHandler.on_quick_play_selected_area(self) -func _restore_cordon_state(enabled: bool, bounds: AABB) -> void: - HFDockManageHandler.restore_cordon_state(self, enabled, bounds) - - func _on_export_playtest() -> void: await HFDockManageHandler.on_export_playtest(self) -func _show_spawn_fix_dialog(spawn: Node3D, validation: Dictionary, mask: int) -> void: - HFDockManageHandler.show_spawn_fix_dialog(self, spawn, validation, mask) - - -func _record_spawn_create_undo(before_state: Dictionary) -> void: - HFDockManageHandler.record_spawn_create_undo(self, before_state) - - -func _record_spawn_move_undo(spawn: Node3D, old_pos: Vector3, new_pos: Vector3) -> void: - HFDockManageHandler.record_spawn_move_undo(self, spawn, old_pos, new_pos) - - -func _restore_spawn(spawn: Node3D, pos: Vector3, angle_deg: float) -> void: - HFDockManageHandler.restore_spawn(spawn, pos, angle_deg) - - func _on_spawn_validate() -> void: await HFDockManageHandler.on_spawn_validate(self) @@ -3652,10 +3584,6 @@ func _on_show_spawn_debug_toggled(enabled: bool) -> void: await HFDockManageHandler.on_show_spawn_debug_toggled(self, enabled) -func _notify_running_instances() -> void: - HFDockManageHandler.notify_running_instances(self) - - func _warn_missing_dependencies() -> void: HFDockManageHandler.warn_missing_dependencies(self) @@ -4247,14 +4175,6 @@ func _refresh_terrain_slots() -> void: HFDockPaintHandler.refresh_terrain_slots(self) -func _terrain_slot_label(path: String) -> String: - return HFDockPaintHandler.terrain_slot_label(path) - - -func _set_terrain_slot_controls_enabled(enabled: bool) -> void: - HFDockPaintHandler.set_terrain_slot_controls_enabled(self, enabled) - - func _on_material_selected(index: int) -> void: _selected_material_index = index @@ -5718,10 +5638,6 @@ func refresh_visgroup_ui() -> void: HFDockVisgroupHandler.refresh_visgroup_ui(self) -func _get_selected_visgroup_name() -> String: - return HFDockVisgroupHandler.get_selected_visgroup_name(self) - - func _on_visgroup_add() -> void: HFDockVisgroupHandler.on_visgroup_add(self) @@ -5763,10 +5679,6 @@ func _setup_cordon_ui() -> void: HFDockVisgroupHandler.setup_cordon_ui(self) -func _make_cordon_spin(min_val: float, max_val: float, default_val: float) -> SpinBox: - return HFDockVisgroupHandler.make_cordon_spin(self, min_val, max_val, default_val) - - func _on_cordon_toggled(pressed: bool) -> void: HFDockVisgroupHandler.on_cordon_toggled(self, pressed) diff --git a/addons/hammerforge/dock_entity_handler.gd b/addons/hammerforge/dock_entity_handler.gd index 8ccefe7a..1dcd8f44 100644 --- a/addons/hammerforge/dock_entity_handler.gd +++ b/addons/hammerforge/dock_entity_handler.gd @@ -26,7 +26,6 @@ static func rebuild_entity_props(dock: Object, entity: Node3D) -> void: return dock._entity_props_section.visible = true - dock._entity_props_entity = entity var content = dock._entity_props_section.get_content() var e_data := HFEntityPropUtils.get_entity_data(entity) @@ -140,7 +139,6 @@ static func clear_entity_props(dock: Object) -> void: if is_instance_valid(ctrl): ctrl.queue_free() dock._entity_props_controls.clear() - dock._entity_props_entity = null if dock._entity_props_section: dock._entity_props_section.visible = false diff --git a/addons/hammerforge/draft_entity.gd b/addons/hammerforge/draft_entity.gd index 89dc43fe..bf7640b0 100644 --- a/addons/hammerforge/draft_entity.gd +++ b/addons/hammerforge/draft_entity.gd @@ -15,12 +15,6 @@ var entity_class: String: var entity_data: Dictionary = {} var preview_node: Node3D = null var _gizmo_update_queued := false -var entity_properties: Dictionary: - get: - return entity_data - set(value): - if value is Dictionary: - entity_data = value func _set_entity_type(val: String) -> void: diff --git a/addons/hammerforge/level_root.gd b/addons/hammerforge/level_root.gd index ca738ecc..7c672403 100644 --- a/addons/hammerforge/level_root.gd +++ b/addons/hammerforge/level_root.gd @@ -618,13 +618,6 @@ var input_state: HFInputStateType: get: return drag_system.input_state if drag_system else null var height_pixels_per_unit := 4.0 - -var drag_active: bool: - get: - return drag_system.input_state.is_dragging() if drag_system else false - set(value): - if not value and drag_system: - drag_system.input_state.cancel() var drag_stage: int: get: return drag_system.input_state.get_drag_stage() if drag_system else 0 @@ -731,7 +724,6 @@ var _face_hover_material: StandardMaterial3D = null var _face_hover_st: SurfaceTool = null var _face_hover_last_brush: Node3D = null var _face_hover_last_face_idx: int = -1 -var grid_plane_axis := AxisLock.Y var grid_plane_origin := Vector3.ZERO var grid_axis_preference := AxisLock.Y var last_brush_center := Vector3.ZERO @@ -935,28 +927,6 @@ func _update_grid_material() -> void: grid_system.update_grid_material() -func _update_grid_transform(axis: int, origin: Vector3) -> void: - if grid_system: - grid_system.update_grid_transform(axis, origin) - - -func _effective_grid_axis() -> int: - return grid_system.effective_grid_axis() if grid_system else AxisLock.Y - - -func _set_grid_plane_origin(origin: Vector3, axis: int) -> void: - if grid_system: - grid_system.set_grid_plane_origin(origin, axis) - - -func _intersect_axis_plane( - camera: Camera3D, mouse_pos: Vector2, axis: int, origin: Vector3 -) -> Variant: - return ( - grid_system.intersect_axis_plane(camera, mouse_pos, axis, origin) if grid_system else null - ) - - # =========================================================================== # Visgroup / Group API (delegates to visgroup_system) # =========================================================================== @@ -1156,15 +1126,6 @@ func is_entity_node(node: Node) -> bool: return entity_system.is_entity_node(node) -## Backward-compat alias — prefer is_entity_node(). -func _is_entity_node(node: Node) -> bool: - return is_entity_node(node) - - -func _capture_entity_info(entity: DraftEntity) -> Dictionary: - return entity_system.capture_entity_info(entity) - - func _restore_entity_from_info(info: Dictionary) -> DraftEntity: return entity_system.restore_entity_from_info(info) @@ -2454,10 +2415,6 @@ func handle_surface_paint_input( ) -func _regenerate_paint_layers() -> void: - paint_system.regenerate_paint_layers() - - func import_heightmap(path: String) -> void: paint_system.import_heightmap(path) @@ -3748,20 +3705,6 @@ static func _local_triangle_pick_distance( return best_t if best_t < INF else -1.0 -func _ray_intersect_sphere(origin: Vector3, dir: Vector3, center: Vector3, radius: float) -> float: - var oc = origin - center - var b = oc.dot(dir) - var c = oc.dot(oc) - radius * radius - var h = b * b - c - if h < 0.0: - return -1.0 - var sqrt_h = sqrt(h) - var t = -b - sqrt_h - if t < 0.0: - t = -b + sqrt_h - return t if t >= 0.0 else -1.0 - - func _ray_intersect_aabb(origin: Vector3, dir: Vector3, aabb: AABB) -> float: var tmin = -INF var tmax = INF diff --git a/addons/hammerforge/map_io.gd b/addons/hammerforge/map_io.gd index 29d32dfc..cdeb5c41 100644 --- a/addons/hammerforge/map_io.gd +++ b/addons/hammerforge/map_io.gd @@ -998,13 +998,6 @@ static func _cylinder_to_map_lines( return lines -static func _format_face_line(a: Vector3, b: Vector3, c: Vector3) -> String: - return ( - "( %s ) ( %s ) ( %s ) %s 0 0 0 1 1" - % [_format_vec3(a), _format_vec3(b), _format_vec3(c), DEFAULT_TEXTURE] - ) - - static func _format_vec3(v: Vector3) -> String: return "%s %s %s" % [_snapped(v.x), _snapped(v.y), _snapped(v.z)] diff --git a/addons/hammerforge/plugin_selection_state.gd b/addons/hammerforge/plugin_selection_state.gd index 1eb730bb..a96ea8b5 100644 --- a/addons/hammerforge/plugin_selection_state.gd +++ b/addons/hammerforge/plugin_selection_state.gd @@ -213,24 +213,6 @@ static func sync_hf_selection_if_empty(plugin: Object) -> void: plugin.hf_selection = selection.get_selected_nodes() -static func selection_has_brush(nodes: Array, root: Node) -> bool: - if not root: - return false - for node in nodes: - if root.is_brush_node(node): - return true - return false - - -static func selection_has_entity(nodes: Array, root: Node) -> bool: - if not root: - return false - for node in nodes: - if root.is_entity_node(node): - return true - return false - - static func classify_selection_scope(nodes: Array, root: Node) -> int: if nodes.is_empty() or not root: return SCOPE_EMPTY diff --git a/addons/hammerforge/systems/hf_drag_system.gd b/addons/hammerforge/systems/hf_drag_system.gd index 46009dd1..6d4e6890 100644 --- a/addons/hammerforge/systems/hf_drag_system.gd +++ b/addons/hammerforge/systems/hf_drag_system.gd @@ -398,12 +398,6 @@ func _update_lock_state(origin: Vector3, current: Vector3) -> void: input_state.locked_thickness.z = abs(current.z - origin.z) -func _pick_axis(origin: Vector3, current: Vector3) -> int: - var dx = abs(current.x - origin.x) - var dz = abs(current.z - origin.z) - return root.AxisLock.X if dx >= dz else root.AxisLock.Z - - # --------------------------------------------------------------------------- # Height # --------------------------------------------------------------------------- diff --git a/addons/hammerforge/systems/hf_grid_system.gd b/addons/hammerforge/systems/hf_grid_system.gd index 77087f4c..65c2c082 100644 --- a/addons/hammerforge/systems/hf_grid_system.gd +++ b/addons/hammerforge/systems/hf_grid_system.gd @@ -58,7 +58,6 @@ func update_grid_material() -> void: func update_grid_transform(axis: int, origin: Vector3) -> void: if not root.grid_mesh or not root.grid_mesh.is_inside_tree(): return - root.grid_plane_axis = axis root.grid_plane_origin = origin var rot = Vector3.ZERO match axis: diff --git a/addons/hammerforge/systems/hf_subtract_preview.gd b/addons/hammerforge/systems/hf_subtract_preview.gd index b62b871a..cd738032 100644 --- a/addons/hammerforge/systems/hf_subtract_preview.gd +++ b/addons/hammerforge/systems/hf_subtract_preview.gd @@ -345,10 +345,6 @@ static func is_valid_aabb(aabb: AABB) -> bool: return aabb.size.x > 0.001 and aabb.size.y > 0.001 and aabb.size.z > 0.001 -static func _is_valid_aabb(aabb: AABB) -> bool: - return is_valid_aabb(aabb) - - ## Operation used for subtract preview. DraftBrush stores CSG operation ints. ## Returns -1 when the node is not a previewable brush. static func preview_operation(node: Node) -> int: @@ -387,7 +383,3 @@ static func world_aabb(node: Node3D) -> AABB: return xform * local_aabb var half_scale := node.scale * 0.5 return AABB(xform.origin - half_scale, node.scale) - - -func _get_world_aabb(node: Node3D) -> AABB: - return world_aabb(node) diff --git a/addons/hammerforge/ui/hf_context_toolbar.gd b/addons/hammerforge/ui/hf_context_toolbar.gd index 8c3fe438..86f5b02d 100644 --- a/addons/hammerforge/ui/hf_context_toolbar.gd +++ b/addons/hammerforge/ui/hf_context_toolbar.gd @@ -38,10 +38,7 @@ var _sections: Dictionary = {} # Context -> Control var _material_thumbs: Array[Button] = [] var _favorite_materials: Array = [] # Array of {index, material, texture} var _brush_count := 0 -var _entity_count := 0 var _face_count := 0 -var _has_root := false -var _is_subtract := false var _keymap = null # HFKeymap diff --git a/addons/hammerforge/ui/hf_hotkey_palette.gd b/addons/hammerforge/ui/hf_hotkey_palette.gd index 9daa133d..7708902f 100644 --- a/addons/hammerforge/ui/hf_hotkey_palette.gd +++ b/addons/hammerforge/ui/hf_hotkey_palette.gd @@ -387,11 +387,6 @@ func _execute_first_match() -> void: return -## Execute the "Did you mean" suggestion (first fuzzy match). -func _accept_suggestion() -> void: - _execute_first_match() - - func _on_entry_pressed(action: String) -> void: visible = false action_invoked.emit(action) diff --git a/addons/hammerforge/ui/hf_io_wiring_panel.gd b/addons/hammerforge/ui/hf_io_wiring_panel.gd index 496d38bc..c2d3544e 100644 --- a/addons/hammerforge/ui/hf_io_wiring_panel.gd +++ b/addons/hammerforge/ui/hf_io_wiring_panel.gd @@ -38,7 +38,6 @@ var _summary_label: Label var _highlight_btn: Button var _outputs_list: ItemList var _outputs_remove: Button -var _targets_list: ItemList var _preset_option: OptionButton var _preset_delete_btn: Button var _preset_apply_btn: Button diff --git a/tests/test_core_loop_overlays.gd b/tests/test_core_loop_overlays.gd index 4c39ed3f..94712bd0 100644 --- a/tests/test_core_loop_overlays.gd +++ b/tests/test_core_loop_overlays.gd @@ -57,14 +57,12 @@ func test_overlay_callbacks_delegate_to_the_overlay_module(): var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") for method_name in [ "handle_double_tap", - "show_quick_property", "on_quick_property_committed", "show_coach_mark_for_action", "show_coach_mark_for_tool_id", "install_power_user_overlays", "teardown_power_user_overlays", "update_vertex_overlay", - "ensure_vertex_overlay", "clear_vertex_overlay", "update_marquee_overlay", "draw_marquee_overlay", diff --git a/tests/test_selection_gesture.gd b/tests/test_selection_gesture.gd index 79037200..18cc1a81 100644 --- a/tests/test_selection_gesture.gd +++ b/tests/test_selection_gesture.gd @@ -445,12 +445,14 @@ func test_face_selection_release_keeps_native_gizmo_cleanup_alive() -> void: func test_plugin_selection_callbacks_are_thin_delegates() -> void: var source := FileAccess.get_file_as_string("res://addons/hammerforge/plugin.gd") + # The entry points plugin.gd owns. Anything a module calls itself is not one: + # plugin.gd used to carry a wrapper for each that nothing called, and lists + # like this are why they survived (#609). for call in [ "HFPluginSelectionInput.handle_press", "HFPluginSelectionInput.custom_release_result", "HFPluginSelectionInput.handle_active", "HFPluginSelectionInput.select_faces_in_rect", - "HFPluginSelectionInput.face_screen_center", ]: assert_true(source.contains(call), "%s must be delegated" % call) @@ -851,16 +853,10 @@ func test_plugin_selection_state_callbacks_are_thin_delegates() -> void: for method_name in [ "on_editor_selection_changed", "finalize_native_selection", - "normalize_editor_selection", "normalize_managed_selection_owner", - "expand_native_group_selection", "expand_native_group_members", - "same_node_selection", "apply_selection_list", "apply_hf_selection", - "sync_hf_selection_if_empty", - "selection_has_brush", - "selection_has_entity", "classify_selection_scope", "guard_hammerforge_shortcut", "managed_surface_action_requirement", From f62d183b6e099e94a3f4b5b6dad3e9954393d6a8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 16 Sep 2026 23:39:35 +0000 Subject: [PATCH 3/3] Update published test counts --- DEVELOPMENT.md | 2 +- HammerForge_SPEC.md | 2 +- docs/features.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index b9e002d5..7f7d7822 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -431,7 +431,7 @@ The project has a GitHub Actions workflow (`.github/workflows/ci.yml`) that runs - `gdformat --check` -- verifies formatting - `gdlint` -- checks lint rules (configured in `.gdlintrc`) - `tools/check_placement_order.py` -- refuses a world transform written to a node that is not in the tree yet -- **GUT unit + integration tests** -- 4,084 tests across 221 test scripts (4,077 passing plus seven intentional no-assert safety tests; 19,725 assertions; verified in CI on September 16, 2026; runs Godot headless) +- **GUT unit + integration tests** -- 4,084 tests across 221 test scripts (4,077 passing plus seven intentional no-assert safety tests; 19,707 assertions; verified in CI on September 16, 2026; runs Godot headless) Run locally before pushing: ``` diff --git a/HammerForge_SPEC.md b/HammerForge_SPEC.md index c1e9007e..cd4368fa 100644 --- a/HammerForge_SPEC.md +++ b/HammerForge_SPEC.md @@ -544,6 +544,6 @@ Unit tests use the [GUT](https://github.com/bitwes/Gut) framework and run headle | `test_selection_gesture.gd` | 40 | Native widget/Object Select ownership, modal Face Select, recovery, focus/scope guards, native duplicate/reparent repair, and Inspector/undo change tracking | | `test_viewport_outlines.gd` | 39 | Sparse semantic outlines, exact/composite entity collision, visibility/transforms, and shape-aware resize recovery | -Full suite (verified in CI on September 16, 2026): **4,084 tests** across **221 scripts** (**4,077 passing** plus seven intentional no-assert safety tests; **19,725 assertions**). +Full suite (verified in CI on September 16, 2026): **4,084 tests** across **221 scripts** (**4,077 passing** plus seven intentional no-assert safety tests; **19,707 assertions**). Tests use root shim scripts (dynamically created GDScript) to provide the LevelRoot interface without circular preload dependencies. Configuration in `.gutconfig.json`. diff --git a/docs/features.md b/docs/features.md index ff1c6069..e374bd64 100644 --- a/docs/features.md +++ b/docs/features.md @@ -387,7 +387,7 @@ transform group while paint mode is on, so only one of the two is ever live. ## Testing -The verified Godot 4.7 suite on September 16, 2026 contains **4,084 tests across 221 scripts**: **4,077 passing tests**, seven intentional no-assert safety tests, and **19,725 assertions**. All checks run on every push and pull request via GitHub Actions. +The verified Godot 4.7 suite on September 16, 2026 contains **4,084 tests across 221 scripts**: **4,077 passing tests**, seven intentional no-assert safety tests, and **19,707 assertions**. All checks run on every push and pull request via GitHub Actions. ```bash # Run all tests headless