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/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/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.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/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/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 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_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) 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",