diff --git a/src/godotiq/tools/code/__init__.py b/src/godotiq/tools/code/__init__.py
index 007fb8d..c14f76a 100644
--- a/src/godotiq/tools/code/__init__.py
+++ b/src/godotiq/tools/code/__init__.py
@@ -298,6 +298,21 @@ _MAX_SIGNALS = 30
 _MAX_BUSIEST = 10
 
 
+def _collect_all_defined_signals(session: GodotIQSession) -> set[str]:
+    """Build project-wide set of all defined signal names.
+
+    Iterates every parsed GdScript in the session and collects
+    signal names from each script's .signals list. Used by the
+    find="missing" code path to avoid false positives from the
+    event bus pattern.
+    """
+    result: set[str] = set()
+    for gd in session._gd_scripts.values():
+        for sig in gd.signals:
+            result.add(sig.name)
+    return result
+
+
 def _build_signal_map(
     session: GodotIQSession,
     scope: str = "all",
@@ -359,6 +374,9 @@ def _build_signal_map(
     total_emissions = sum(len(v) for v in emissions.values())
     total_connections = sum(len(v) for v in listeners.values())
 
+    # Project-wide signal definitions for the missing check (event bus pattern)
+    all_defined_signals = _collect_all_defined_signals(session)
+
     signals_list: list[dict] = []
     orphans_list: list[dict] = []
     missing_list: list[dict] = []
@@ -390,17 +408,13 @@ def _build_signal_map(
                 "note": "Signal emitted but nobody listens",
             })
 
-        # Missing: emitted but not defined in the emitting file
-        for emitter_path in unique_emitters:
-            emitter_gd = session.get_script(emitter_path)
-            if emitter_gd is None:
-                continue
-            emitter_defs = {s.name for s in emitter_gd.signals}
-            if name not in emitter_defs:
+        # Missing: emitted but not defined anywhere in the project
+        if name not in all_defined_signals:
+            for emitter_path in unique_emitters:
                 missing_list.append({
                     "name": name,
                     "emitted_in": emitter_path,
-                    "note": "Emitted but no signal definition found in that file",
+                    "note": "Emitted but no signal definition found in the project",
                 })
 
     # Busiest: sort by listener_count descending
diff --git a/tests/test_tools/test_signal_map.py b/tests/test_tools/test_signal_map.py
new file mode 100644
index 0000000..ee167ac
--- /dev/null
+++ b/tests/test_tools/test_signal_map.py
@@ -0,0 +1,146 @@
+"""Tests for godotiq_signal_map tool."""
+
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from unittest.mock import patch
+
+import pytest
+
+from godotiq.session import GodotIQSession
+from godotiq.tools.code import _build_signal_map
+
+
+class TestSignalMapAll:
+    def test_signal_map_all(self, session: GodotIQSession) -> None:
+        """Returns total_signals, total_emissions, total_connections as integers."""
+        result = _build_signal_map(session)
+        assert isinstance(result["total_signals_defined"], int)
+        assert isinstance(result["total_emissions"], int)
+        assert isinstance(result["total_connections"], int)
+        # Fixture has 6 signals defined (3 in economy, 3 in progression)
+        assert result["total_signals_defined"] == 6
+        # Emissions: cash_changed x2, insufficient_funds x1, xp_gained x1, level_up x1
+        assert result["total_emissions"] == 5
+        assert "signals" in result
+
+    def test_signal_map_orphans(self, session: GodotIQSession) -> None:
+        """Orphans list has signals with 0 listeners."""
+        result = _build_signal_map(session, find="orphans")
+        assert "orphans" in result
+        # No signal_connections in fixture → all emitted signals are orphans
+        orphan_names = [o["name"] for o in result["orphans"]]
+        assert "cash_changed" in orphan_names
+        for orphan in result["orphans"]:
+            assert orphan["listeners"] == 0
+
+    def test_signal_map_busiest(self, session: GodotIQSession) -> None:
+        """Busiest sorted by listener_count descending."""
+        result = _build_signal_map(session, find="busiest")
+        assert "busiest" in result
+        listeners = [b["listeners"] for b in result["busiest"]]
+        assert listeners == sorted(listeners, reverse=True)
+
+    def test_signal_map_missing(self, session: GodotIQSession) -> None:
+        """Detects emitted signals not defined in the emitting file."""
+        result = _build_signal_map(session, find="missing")
+        assert "missing" in result
+        # In the fixture, all emissions match definitions in the same file
+        assert len(result["missing"]) == 0
+
+        # Patch: add a script that emits a signal it doesn't define
+        from godotiq.parsers.gd_parser import GdScript, GdSignalEmission
+        fake_gd = GdScript()
+        fake_gd.signal_emissions = [GdSignalEmission(signal_name="phantom_signal", arg_count=0, line=10)]
+        session._gd_scripts["res://scripts/fake.gd"] = fake_gd
+        try:
+            result2 = _build_signal_map(session, find="missing")
+            missing_names = [m["name"] for m in result2["missing"]]
+            assert "phantom_signal" in missing_names
+        finally:
+            del session._gd_scripts["res://scripts/fake.gd"]
+
+
+class TestSignalMapScope:
+    def test_signal_map_scope_file(self, session: GodotIQSession) -> None:
+        """Filters to single file's signals only."""
+        result = _build_signal_map(
+            session, scope="file:res://scripts/autoloads/economy_manager.gd"
+        )
+        assert result["total_signals_defined"] == 3
+        # Only economy_manager signals
+        if "signals" in result:
+            for sig in result["signals"]:
+                for d in sig["defined_in"]:
+                    assert "economy_manager" in d
+
+    def test_signal_map_scope_autoloads(self, session: GodotIQSession) -> None:
+        """Filters to autoload scripts only."""
+        result = _build_signal_map(session, scope="autoloads")
+        # economy_manager + progression_manager are autoloads (both have signals)
+        assert result["total_signals_defined"] == 6
+        if "signals" in result:
+            for sig in result["signals"]:
+                for d in sig["defined_in"]:
+                    assert "autoloads" in d or "economy" in d or "progression" in d
+
+
+class TestSignalMapEventBus:
+    def test_signal_map_events_bus_not_missing(self, session: GodotIQSession) -> None:
+        """Signal emitted via Events bus but defined in events.gd should NOT be flagged as missing."""
+        from godotiq.parsers.gd_parser import GdScript, GdSignal, GdSignalEmission
+
+        events_gd = GdScript()
+        events_gd.signals = [
+            GdSignal(name="order_accepted", args=[], line=1),
+            GdSignal(name="day_started", args=[], line=2),
+        ]
+        emitter_gd = GdScript()
+        emitter_gd.signal_emissions = [
+            GdSignalEmission(signal_name="order_accepted", arg_count=0, line=5),
+        ]
+        session._gd_scripts["res://scripts/autoloads/events_bus.gd"] = events_gd
+        session._gd_scripts["res://scripts/bus_emitter.gd"] = emitter_gd
+        try:
+            result = _build_signal_map(session, find="missing")
+            assert "missing" in result
+            missing_names = [m["name"] for m in result["missing"]]
+            assert "order_accepted" not in missing_names
+        finally:
+            del session._gd_scripts["res://scripts/autoloads/events_bus.gd"]
+            del session._gd_scripts["res://scripts/bus_emitter.gd"]
+
+    def test_signal_map_truly_missing(self, session: GodotIQSession) -> None:
+        """Signal emitted but not defined anywhere in the project is flagged as missing."""
+        from godotiq.parsers.gd_parser import GdScript, GdSignalEmission
+
+        broken_gd = GdScript()
+        broken_gd.signal_emissions = [
+            GdSignalEmission(signal_name="totally_fake_signal", arg_count=0, line=10),
+        ]
+        session._gd_scripts["res://scripts/broken_emitter.gd"] = broken_gd
+        try:
+            result = _build_signal_map(session, find="missing")
+            missing_names = [m["name"] for m in result["missing"]]
+            assert "totally_fake_signal" in missing_names
+            matching = [m for m in result["missing"] if m["name"] == "totally_fake_signal"]
+            assert len(matching) == 1
+            assert matching[0]["emitted_in"] == "res://scripts/broken_emitter.gd"
+        finally:
+            del session._gd_scripts["res://scripts/broken_emitter.gd"]
+
+
+class TestSignalMapTruncation:
+    def test_signal_map_truncation(self, session: GodotIQSession) -> None:
+        """Signals list capped at 30 entries."""
+        # Inject many fake signals to test truncation
+        from godotiq.parsers.gd_parser import GdScript, GdSignal
+        fake_gd = GdScript()
+        fake_gd.signals = [GdSignal(name=f"sig_{i}", args=[], line=i) for i in range(40)]
+        session._gd_scripts["res://scripts/many_signals.gd"] = fake_gd
+        try:
+            result = _build_signal_map(session)
+            assert len(result["signals"]) <= 30
+            assert result.get("truncated") is True
+        finally:
+            del session._gd_scripts["res://scripts/many_signals.gd"]
