diff --git a/godot-addon/addons/godotiq/godotiq_debugger.gd b/godot-addon/addons/godotiq/godotiq_debugger.gd
index 08aab19..2616ab8 100644
--- a/godot-addon/addons/godotiq/godotiq_debugger.gd
+++ b/godot-addon/addons/godotiq/godotiq_debugger.gd
@@ -1,5 +1,77 @@
 @tool
 extends EditorDebuggerPlugin
-## Stub — replaced by Section 03.
+## GodotIQ editor-side debugger plugin — bridges EngineDebugger messages
+## between the running game and the WebSocket server.
 
-var server
+var server  # untyped to avoid circular dependency — set by godotiq_plugin.gd
+var _session_id: int = -1
+
+
+func _has_capture(prefix: String) -> bool:
+	return prefix == "godotiq"
+
+
+func _capture(message: String, data: Array, session_id: int) -> bool:
+	if server == null:
+		push_warning("GodotIQ debugger: received message but server is null")
+		return false
+	match message:
+		"godotiq:screenshot_result":
+			server.handle_game_response("godotiq:screenshot", data)
+			return true
+		"godotiq:perf_result":
+			server.handle_game_response("godotiq:query_perf", data)
+			return true
+		"godotiq:state_result":
+			server.handle_game_response("godotiq:query_state", data)
+			return true
+		"godotiq:input_result":
+			server.handle_game_response("godotiq:input", data)
+			return true
+		"godotiq:exec_result":
+			server.handle_game_response("godotiq:exec", data)
+			return true
+		"godotiq:nav_result":
+			server.handle_game_response("godotiq:nav", data)
+			return true
+		"godotiq:error":
+			server.send_event("runtime_error", {"data": data})
+			return true
+		"godotiq:watch_update":
+			server.send_event("watch_update", {"data": data})
+			return true
+		_:
+			push_warning("GodotIQ debugger: unknown message '%s'" % message)
+			return false
+
+
+func _setup_session(session_id: int) -> void:
+	_session_id = session_id
+	var session := get_session(session_id)
+	if session == null:
+		push_warning("GodotIQ debugger: could not get session %d" % session_id)
+		return
+	session.started.connect(_on_game_started)
+	session.stopped.connect(_on_game_stopped)
+
+
+func _on_game_started() -> void:
+	if server:
+		server.on_game_started()
+
+
+func _on_game_stopped() -> void:
+	if server:
+		server.on_game_stopped()
+	_session_id = -1
+
+
+func send_to_game(message: String, data: Array) -> void:
+	if _session_id < 0:
+		push_warning("GodotIQ debugger: no active session, cannot send '%s'" % message)
+		return
+	var session := get_session(_session_id)
+	if session == null:
+		push_warning("GodotIQ debugger: session %d not found" % _session_id)
+		return
+	session.send_message(message, data)
diff --git a/godot-addon/addons/godotiq/godotiq_runtime.gd b/godot-addon/addons/godotiq/godotiq_runtime.gd
new file mode 100644
index 0000000..d48ee59
--- /dev/null
+++ b/godot-addon/addons/godotiq/godotiq_runtime.gd
@@ -0,0 +1,85 @@
+extends Node
+## GodotIQ game runtime autoload — executes screenshot and performance
+## snapshot requests relayed from the editor via EngineDebugger.
+## Registered as "GodotIQRuntime" autoload by godotiq_plugin.gd.
+## NO @tool — this runs in the game process, not the editor.
+
+
+func _ready() -> void:
+	if not EngineDebugger.is_active():
+		queue_free()
+		return
+	EngineDebugger.register_message_capture("godotiq", _on_debugger_message)
+
+
+func _on_debugger_message(message: String, data: Array) -> bool:
+	match message:
+		"godotiq:screenshot":
+			_take_screenshot(data[0] if data.size() > 0 and data[0] is Dictionary else {})
+			return true
+		"godotiq:query_perf":
+			_send_perf_snapshot()
+			return true
+	return false
+
+
+func _take_screenshot(params: Dictionary) -> void:
+	await get_tree().process_frame
+
+	var viewport := get_viewport()
+	if viewport == null:
+		EngineDebugger.send_message("godotiq:error", ["No viewport available"])
+		return
+
+	var img := viewport.get_texture().get_image()
+	if img == null:
+		EngineDebugger.send_message("godotiq:error", ["Failed to capture viewport image"])
+		return
+
+	var scale: float = params.get("scale", 0.5)
+	var fmt: String = params.get("format", "webp")
+	var w := img.get_width()
+	var h := img.get_height()
+
+	if scale < 1.0:
+		img.resize(int(w * scale), int(h * scale))
+
+	var buffer: PackedByteArray
+	match fmt:
+		"png":
+			buffer = img.save_png_to_buffer()
+		"jpg":
+			buffer = img.save_jpg_to_buffer(0.85)
+		_:
+			fmt = "webp"
+			buffer = img.save_webp_to_buffer()
+
+	var b64 := Marshalls.raw_to_base64(buffer)
+	EngineDebugger.send_message("godotiq:screenshot_result", [b64, fmt, w, h])
+
+
+func _send_perf_snapshot() -> void:
+	var result := {
+		"fps": Engine.get_frames_per_second(),
+		"draw_calls": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_TOTAL_DRAW_CALLS_IN_FRAME
+		),
+		"triangles": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_TOTAL_PRIMITIVES_IN_FRAME
+		),
+		"objects": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_TOTAL_OBJECTS_IN_FRAME
+		),
+		"texture_mem": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_TEXTURE_MEM_USED
+		),
+		"buffer_mem": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_BUFFER_MEM_USED
+		),
+		"video_mem": RenderingServer.get_rendering_info(
+			RenderingServer.RENDERING_INFO_VIDEO_MEM_USED
+		),
+		"total_nodes": get_tree().get_node_count(),
+		"orphan_nodes": Performance.get_monitor(Performance.OBJECT_ORPHAN_NODE_COUNT),
+	}
+	EngineDebugger.send_message("godotiq:perf_result", [JSON.stringify(result)])
diff --git a/src/godotiq/tools/assets/__init__.py b/src/godotiq/tools/assets/__init__.py
index dcaa5bc..ef3bc2d 100644
--- a/src/godotiq/tools/assets/__init__.py
+++ b/src/godotiq/tools/assets/__init__.py
@@ -321,6 +321,25 @@ def _find_ext_resource_refs(
     ]
 
 
+_NON_VISUAL_TYPES = {
+    "Marker3D", "Node3D", "Position3D", "RemoteTransform3D",
+    "BoneAttachment3D", "SpringArm3D", "NavigationAgent3D",
+}
+
+_UI_SUBSTRINGS = {"Container", "Panel", "Button", "Label"}
+
+
+def _is_visual_node(wn) -> bool:
+    """Check if a WorldNode represents visible geometry, not just a marker/placeholder."""
+    node_type = wn.node.type
+    # Exclude Control-based UI nodes
+    if node_type == "Control" or any(s in node_type for s in _UI_SUBSTRINGS):
+        return False
+    if node_type in _NON_VISUAL_TYPES and not wn.instance_source:
+        return False
+    return True
+
+
 def _reference_mode(
     scene: str,
     intended_use: str,
@@ -337,6 +356,8 @@ def _reference_mode(
         )
         if name_match or source_match:
             matched.append(wn)
+    # Filter to visible geometry only — excludes Marker3D/bare Node3D placeholders
+    matched = [wn for wn in matched if _is_visual_node(wn)]
 
     if not matched:
         return {
@@ -359,9 +380,18 @@ def _reference_mode(
         for wn in matched
     ]
 
-    scales_x = [wn.world_scale[0] for wn in matched]
-    scales_y = [wn.world_scale[1] for wn in matched]
-    scales_z = [wn.world_scale[2] for wn in matched]
+    # Prioritize GLB/GLTF models for recommended_scale
+    glb_matches = [
+        wn for wn in matched
+        if (wn.instance_source and wn.instance_source.lower().endswith((".glb", ".gltf")))
+        or "Model" in wn.node.name or "Mesh" in wn.node.name
+    ]
+    scale_source = glb_matches if glb_matches else matched
+    recommendation_source = "glb_models" if glb_matches else "all_matches"
+
+    scales_x = [wn.world_scale[0] for wn in scale_source]
+    scales_y = [wn.world_scale[1] for wn in scale_source]
+    scales_z = [wn.world_scale[2] for wn in scale_source]
     recommended_scale = [
         statistics.median(scales_x),
         statistics.median(scales_y),
@@ -374,6 +404,7 @@ def _reference_mode(
         "intended_use": intended_use,
         "matches_found": len(matched),
         "references": references,
+        "recommendation_source": recommendation_source,
         "scale_range": {
             "min": [min(scales_x), min(scales_y), min(scales_z)],
             "max": [max(scales_x), max(scales_y), max(scales_z)],
@@ -409,6 +440,9 @@ def _build_suggest_scale(
     if intended_use is None:
         intended_use = ""
 
+    if model is not None and not model.strip():
+        model = None
+
     if model is None and not intended_use:
         return {"error": "Either 'model' or 'intended_use' must be provided"}
 
diff --git a/src/godotiq/tools/memory/__init__.py b/src/godotiq/tools/memory/__init__.py
index e2e2224..aa82c34 100644
--- a/src/godotiq/tools/memory/__init__.py
+++ b/src/godotiq/tools/memory/__init__.py
@@ -169,6 +169,12 @@ def _build_full(session: GodotIQSession) -> dict:
     all_emissions = session.get_all_signal_emissions()
     all_connections_raw = session.get_all_signal_connections()
 
+    # Build project-defined signal set (signals declared in .gd files)
+    project_signals: set[str] = set()
+    for gd in session._gd_scripts.values():
+        for sig in gd.signals:
+            project_signals.add(sig.name)
+
     # Normalize connection keys: "EconomyManager.cash_changed" -> "cash_changed"
     all_connections_by_name: dict[str, list[str]] = {}
     for source, scripts in all_connections_raw.items():
@@ -182,16 +188,18 @@ def _build_full(session: GodotIQSession) -> dict:
     total_emissions = sum(len(v) for v in all_emissions.values())
     total_connections = sum(len(v) for v in all_connections_by_name.values())
 
-    # Orphan = emitted but never connected-to
+    # Orphan = project-defined signal emitted but never connected-to
     orphan_count = sum(
-        1 for sig_name in all_emissions if sig_name not in all_connections_by_name
+        1 for sig_name in all_emissions
+        if sig_name in project_signals and sig_name not in all_connections_by_name
     )
 
-    # Top signals by listener count
+    # Top signals by listener count — project-defined only
     top_signals = sorted(
         [
             {"name": sig, "listeners": len(scripts)}
             for sig, scripts in all_connections_by_name.items()
+            if sig in project_signals
         ],
         key=lambda e: e["listeners"],
         reverse=True,
diff --git a/src/godotiq/tools/spatial/__init__.py b/src/godotiq/tools/spatial/__init__.py
index 59b71f3..f591e47 100644
--- a/src/godotiq/tools/spatial/__init__.py
+++ b/src/godotiq/tools/spatial/__init__.py
@@ -526,7 +526,7 @@ def _check_z_fighting(world_nodes: list[WorldNode]) -> list[dict]:
             dist = _compute_distance(a.world_position, b.world_position)
             if dist < _Z_FIGHTING_DISTANCE and a.node.name != b.node.name:
                 seen.add(pair_key)
-                severity = "info" if _parent_path(a.full_path) == _parent_path(b.full_path) else "warning"
+                severity = "info"  # z-fighting is always cosmetic, never a gameplay bug
                 issues.append({
                     "check": "z_fighting",
                     "node": a.node.name,
diff --git a/tests/test_tools/test_project_summary.py b/tests/test_tools/test_project_summary.py
index eb3aefd..ae9f2b4 100644
--- a/tests/test_tools/test_project_summary.py
+++ b/tests/test_tools/test_project_summary.py
@@ -298,6 +298,29 @@ class TestProjectSummaryNoExtrasAtLowerDetail:
         assert "autoload_analysis" not in result
 
 
+class TestProjectSummarySignalHealthNoBuiltins:
+    """top_signals should only contain project-defined signals, not built-in Godot signals."""
+
+    def test_signal_health_top_signals_no_builtins(self, session: GodotIQSession) -> None:
+        """top_signals contains only signals declared in project .gd files."""
+        from godotiq.tools.memory import _build_project_summary
+
+        result = _build_project_summary(session, "full")
+        top = result["signal_health"]["top_signals"]
+
+        # Build the project-defined signal set
+        project_signals: set[str] = set()
+        for gd in session._gd_scripts.values():
+            for sig in gd.signals:
+                project_signals.add(sig.name)
+
+        # Every signal in top_signals must be project-defined
+        for entry in top:
+            assert entry["name"] in project_signals, (
+                f"Built-in signal '{entry['name']}' should not appear in top_signals"
+            )
+
+
 class TestProjectSummaryMCPRegistration:
     """Verify the tool is registered on the MCP server."""
 
diff --git a/tests/test_tools/test_spatial_audit.py b/tests/test_tools/test_spatial_audit.py
index cc8261f..ef3f7fe 100644
--- a/tests/test_tools/test_spatial_audit.py
+++ b/tests/test_tools/test_spatial_audit.py
@@ -381,9 +381,13 @@ class TestSpatialAuditSiblingZFightingDowngraded:
         assert issues[0]["severity"] == "info"
 
 
-class TestSpatialAuditNonSiblingZFightingWarning:
-    def test_non_sibling_z_fighting_stays_warning(self) -> None:
-        """Two mesh nodes under different parents at identical positions → severity 'warning'."""
+class TestSpatialAuditNonSiblingZFightingAlsoInfo:
+    def test_non_sibling_z_fighting_also_info(self) -> None:
+        """Two mesh nodes under different parents at identical positions → severity 'info'.
+
+        ALL z-fighting is cosmetic (flickering surfaces), never a gameplay bug,
+        regardless of parent relationship.
+        """
         from godotiq.parsers.tscn_parser import TscnNode
         from godotiq.parsers.scene_resolver import WorldNode
         from godotiq.tools.spatial import _check_z_fighting
@@ -410,7 +414,7 @@ class TestSpatialAuditNonSiblingZFightingWarning:
         )
         issues = _check_z_fighting([node_a, node_b])
         assert len(issues) == 1
-        assert issues[0]["severity"] == "warning"
+        assert issues[0]["severity"] == "info"
         assert issues[0]["check"] == "z_fighting"
 
 
diff --git a/tests/test_tools/test_suggest_scale.py b/tests/test_tools/test_suggest_scale.py
index ba6b63f..da44b9d 100644
--- a/tests/test_tools/test_suggest_scale.py
+++ b/tests/test_tools/test_suggest_scale.py
@@ -219,6 +219,18 @@ class TestSuggestScaleReferenceMode:
         )
         assert "error" in result
 
+    def test_suggest_scale_empty_string_model_triggers_reference_mode(self, session: GodotIQSession) -> None:
+        """model='' should behave same as model=None (enter reference mode)."""
+        result = _build_suggest_scale(
+            session,
+            "res://scenes/equipment/fdm_printer.tscn",
+            model="",
+            intended_use="printer",
+        )
+        assert result["mode"] == "reference"
+        assert result["matches_found"] > 0
+        assert result["recommended_scale"] is not None
+
     def test_suggest_scale_model_still_works(self, session: GodotIQSession) -> None:
         """Existing model-based behavior is unchanged, now includes mode='model'."""
         result = _build_suggest_scale(
@@ -229,3 +241,135 @@ class TestSuggestScaleReferenceMode:
         assert result["mode"] == "model"
         assert result["recommended_scale"] is not None
         assert result["match_tier"] == "exact_filename"
+
+
+class TestSuggestScaleReferenceModeExcludesUI:
+    """Reference mode skips Control-derived nodes (Panel, Button, etc.)."""
+
+    def test_suggest_scale_reference_mode_excludes_ui_nodes(self) -> None:
+        """Reference mode skips Control-derived nodes (Panel, Button, etc.)."""
+        from godotiq.parsers.tscn_parser import TscnNode
+        from godotiq.parsers.scene_resolver import WorldNode
+        from godotiq.tools.assets import _reference_mode
+
+        panel = WorldNode(
+            node=TscnNode(name="printer_info_panel", type="PanelContainer"),
+            world_position=(0.0, 0.0, 0.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(1.0, 1.0, 1.0),
+            full_path="UI/printer_info_panel",
+            depth=2,
+            is_instance_root=False,
+            instance_source=None,
+        )
+        button = WorldNode(
+            node=TscnNode(name="printer_buy_button", type="Button"),
+            world_position=(0.0, 0.0, 0.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(2.0, 2.0, 2.0),
+            full_path="UI/printer_buy_button",
+            depth=2,
+            is_instance_root=False,
+            instance_source=None,
+        )
+        control = WorldNode(
+            node=TscnNode(name="printer_control", type="Control"),
+            world_position=(0.0, 0.0, 0.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(3.0, 3.0, 3.0),
+            full_path="UI/printer_control",
+            depth=2,
+            is_instance_root=False,
+            instance_source=None,
+        )
+        visual = WorldNode(
+            node=TscnNode(name="PrinterModel", type="MeshInstance3D"),
+            world_position=(2.0, 0.0, 3.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(0.35, 0.35, 0.35),
+            full_path="Room/PrinterModel",
+            depth=2,
+            is_instance_root=True,
+            instance_source="res://assets/models/printers/printer_standard.glb",
+        )
+        result = _reference_mode(
+            "res://scenes/main.tscn", "printer", [panel, button, control, visual]
+        )
+        assert result["matches_found"] == 1
+        assert result["references"][0]["name"] == "PrinterModel"
+        assert result["recommended_scale"] == [0.35, 0.35, 0.35]
+
+
+class TestSuggestScaleReferenceModeGLBPriority:
+    """When GLB models exist among matches, recommended_scale uses only GLB scales."""
+
+    def test_suggest_scale_reference_mode_prioritizes_glb(self) -> None:
+        """When GLB models exist among matches, recommended_scale uses only GLB scales."""
+        from godotiq.parsers.tscn_parser import TscnNode
+        from godotiq.parsers.scene_resolver import WorldNode
+        from godotiq.tools.assets import _reference_mode
+
+        glb_node = WorldNode(
+            node=TscnNode(name="printer_glb", type="Node3D"),
+            world_position=(0.0, 0.0, 0.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(0.5, 0.5, 0.5),
+            full_path="Room/printer_glb",
+            depth=2,
+            is_instance_root=True,
+            instance_source="res://assets/models/printers/printer.glb",
+        )
+        non_glb = WorldNode(
+            node=TscnNode(name="printer_light", type="OmniLight3D"),
+            world_position=(0.0, 2.0, 0.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(10.0, 10.0, 10.0),
+            full_path="Room/printer_light",
+            depth=2,
+            is_instance_root=False,
+            instance_source=None,
+        )
+        result = _reference_mode(
+            "res://scenes/main.tscn", "printer", [glb_node, non_glb]
+        )
+        assert result["matches_found"] == 2
+        assert result["recommendation_source"] == "glb_models"
+        # recommended_scale from GLB node only (0.5), not the light (10.0)
+        assert result["recommended_scale"] == [0.5, 0.5, 0.5]
+
+
+class TestSuggestScaleReferenceModeVisualFilter:
+    """Reference mode excludes non-visual marker/placeholder nodes."""
+
+    def test_suggest_scale_reference_mode_excludes_markers(self) -> None:
+        """Reference mode skips Marker3D/bare Node3D nodes, only returns visual geometry."""
+        from godotiq.parsers.tscn_parser import TscnNode
+        from godotiq.parsers.scene_resolver import WorldNode
+        from godotiq.tools.assets import _reference_mode
+
+        marker = WorldNode(
+            node=TscnNode(name="printer_slot_1", type="Marker3D"),
+            world_position=(2.0, 0.0, 3.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(1.0, 1.0, 1.0),
+            full_path="Room/printer_slot_1",
+            depth=2,
+            is_instance_root=False,
+            instance_source=None,
+        )
+        visual = WorldNode(
+            node=TscnNode(name="PrinterModel", type="Node3D"),
+            world_position=(2.0, 0.0, 3.0),
+            world_rotation=(0.0, 0.0, 0.0),
+            world_scale=(0.35, 0.35, 0.35),
+            full_path="Room/PrinterModel",
+            depth=2,
+            is_instance_root=True,
+            instance_source="res://scenes/equipment/fdm_printer.tscn",
+        )
+        result = _reference_mode(
+            "res://scenes/main.tscn", "printer", [marker, visual]
+        )
+        assert result["matches_found"] == 1
+        assert result["references"][0]["name"] == "PrinterModel"
+        assert result["recommended_scale"] == [0.35, 0.35, 0.35]
