diff --git a/src/godotiq/tools/spatial/__init__.py b/src/godotiq/tools/spatial/__init__.py
index 56e8bde..094fe26 100644
--- a/src/godotiq/tools/spatial/__init__.py
+++ b/src/godotiq/tools/spatial/__init__.py
@@ -192,6 +192,8 @@ def _build_scene_map(
         world_nodes = _filter_by_include(world_nodes, include)
 
     # Focus mode: filter by radius around focus node
+    nearby_total_before_cap = 0
+    nearby_capped = False
     if focus_node is not None:
         world_nodes = find_nodes_in_radius(
             world_nodes, focus_node.world_position, radius
@@ -199,34 +201,62 @@ def _build_scene_map(
         # Ensure focus node itself is in results even if filtered by include
         if focus_node not in world_nodes:
             world_nodes.insert(0, focus_node)
-        # Sort by distance to focus, cap at _MAX_NODES_WITH_FOCUS
+        # Sort by distance to focus
         world_nodes.sort(
             key=lambda wn: _compute_distance(
                 focus_node.world_position, wn.world_position
             )
         )
-        world_nodes = world_nodes[:_MAX_NODES_WITH_FOCUS]
+        # Detail-dependent cap
+        cap = _MAX_NEARBY.get(detail, 20)
+        nearby_total_before_cap = len(world_nodes)
+        nearby_capped = nearby_total_before_cap > cap
+        world_nodes = world_nodes[:cap]
     else:
         # No focus: top-level only (root + direct children), cap at 50
         world_nodes = [wn for wn in world_nodes if wn.depth <= 1]
         world_nodes = world_nodes[:_MAX_NODES_NO_FOCUS]
 
     # Build node representations based on detail level
-    if detail == "full":
-        node_dicts = [_node_to_full(wn) for wn in world_nodes]
-    elif detail == "brief":
-        node_dicts = [_node_to_brief(wn) for wn in world_nodes]
-    else:
-        node_dicts = [_node_to_normal(wn) for wn in world_nodes]
-
-    # Add focus-specific fields to each node
     if focus_node is not None:
-        for nd, wn in zip(node_dicts, world_nodes):
+        # Focus mode: compact representations
+        node_dicts = []
+        for wn in world_nodes:
             dist = _compute_distance(focus_node.world_position, wn.world_position)
-            nd["distance_to_focus"] = round(dist, 4)
-            nd["direction_from_focus"] = _compute_direction(
-                focus_node.world_position, wn.world_position
-            )
+            nd: dict = {
+                "name": wn.node.name,
+                "type": wn.node.type,
+                "distance_to_focus": round(dist, 4),
+                "direction_from_focus": _compute_direction(
+                    focus_node.world_position, wn.world_position
+                ),
+            }
+            if detail == "normal":
+                nd["groups"] = list(wn.node.groups)
+                nd["has_script"] = wn.node.script is not None
+            elif detail == "full":
+                nd["full_path"] = wn.full_path
+                nd["position"] = list(wn.world_position)
+                nd["rotation_deg"] = [math.degrees(r) for r in wn.world_rotation]
+                nd["scale"] = list(wn.world_scale)
+                nd["groups"] = list(wn.node.groups)
+                nd["script"] = wn.node.script
+                nd["is_instance"] = wn.is_instance_root
+                nd["instance_source"] = wn.instance_source
+                nd["properties"] = dict(wn.node.properties)
+                nd["metadata"] = dict(wn.node.metadata)
+            node_dicts.append(nd)
+    else:
+        if detail == "full":
+            node_dicts = [_node_to_full(wn) for wn in world_nodes]
+        elif detail == "brief":
+            node_dicts = [_node_to_brief(wn) for wn in world_nodes]
+        else:
+            node_dicts = [_node_to_normal(wn) for wn in world_nodes]
+
+    # Add non-focus subtree counts
+    if focus_node is not None:
+        pass  # focus fields already built inline above
     else:
         # No focus: add subtree counts
         for nd, wn in zip(node_dicts, world_nodes):
@@ -250,6 +280,14 @@ def _build_scene_map(
         "nodes": node_dicts,
     }
 
+    # Token hints
+    result["token_hint"] = detail
+    if focus_node is not None:
+        result["nearby_count"] = len(node_dicts)
+        result["nearby_capped"] = nearby_capped
+        if nearby_capped:
+            result["nearby_total_in_radius"] = nearby_total_before_cap
+
     # Add focus metadata
     if focus_node is not None:
         result["focus_node"] = focus_node.full_path or focus_node.node.name
@@ -308,7 +346,8 @@ def _compute_type_summary(nodes: list[WorldNode]) -> dict[str, int]:
 
 
 _MAX_NODES_NO_FOCUS = 50
-_MAX_NODES_WITH_FOCUS = 30
+_MAX_NODES_WITH_FOCUS = 30  # legacy, kept for reference
+_MAX_NEARBY: dict[str, int] = {"brief": 10, "normal": 20, "full": 30}
 _MAX_DISTANCE_PAIRS = 50
 
 
diff --git a/tests/test_tools/test_scene_map.py b/tests/test_tools/test_scene_map.py
index 8bcaf10..c69481c 100644
--- a/tests/test_tools/test_scene_map.py
+++ b/tests/test_tools/test_scene_map.py
@@ -236,6 +236,82 @@ class TestSceneMapFuzzyFocus:
         assert "focus_alternatives" not in result
 
 
+class TestSceneMapDetailCapsAndTokenHints:
+    """Detail-dependent nearby node caps and token hints."""
+
+    def test_scene_map_nearby_cap_brief(self, session: GodotIQSession) -> None:
+        """Brief detail caps nearby nodes at 10."""
+        result = _build_scene_map(
+            session,
+            "res://scenes/main.tscn",
+            focus="Main",
+            radius=100.0,
+            detail="brief",
+        )
+        assert len(result["nodes"]) <= 10
+        assert "nearby_count" in result
+        assert result["token_hint"] == "brief"
+
+    def test_scene_map_nearby_cap_normal(self, session: GodotIQSession) -> None:
+        """Normal detail caps nearby nodes at 20."""
+        result = _build_scene_map(
+            session,
+            "res://scenes/main.tscn",
+            focus="Main",
+            radius=100.0,
+            detail="normal",
+        )
+        assert len(result["nodes"]) <= 20
+        assert "nearby_count" in result
+        assert result["token_hint"] == "normal"
+
+    def test_scene_map_brief_compact_output(self, session: GodotIQSession) -> None:
+        """Brief focus mode nearby nodes have only compact keys."""
+        result = _build_scene_map(
+            session,
+            "res://scenes/equipment/fdm_printer.tscn",
+            focus="FDMPrinter",
+            radius=100.0,
+            detail="brief",
+        )
+        allowed_keys = {"name", "type", "distance_to_focus", "direction_from_focus"}
+        for node in result["nodes"]:
+            assert set(node.keys()) == allowed_keys, (
+                f"Brief focus node has unexpected keys: {set(node.keys()) - allowed_keys}"
+            )
+
+    def test_scene_map_capped_shows_total(self, session: GodotIQSession) -> None:
+        """When nearby_capped is True, nearby_total_in_radius is present."""
+        result = _build_scene_map(
+            session,
+            "res://scenes/main.tscn",
+            focus="Main",
+            radius=100.0,
+            detail="brief",
+        )
+        if result.get("nearby_capped"):
+            assert "nearby_total_in_radius" in result
+            assert result["nearby_total_in_radius"] > 10
+        else:
+            # Not enough nodes to trigger cap — verify key is absent
+            assert "nearby_total_in_radius" not in result
+
+    def test_scene_map_normal_compact_output(self, session: GodotIQSession) -> None:
+        """Normal focus mode nearby nodes include groups and has_script."""
+        result = _build_scene_map(
+            session,
+            "res://scenes/equipment/fdm_printer.tscn",
+            focus="FDMPrinter",
+            radius=100.0,
+            detail="normal",
+        )
+        allowed_keys = {"name", "type", "distance_to_focus", "direction_from_focus", "groups", "has_script"}
+        for node in result["nodes"]:
+            assert set(node.keys()) == allowed_keys, (
+                f"Normal focus node has unexpected keys: {set(node.keys()) - allowed_keys}"
+            )
+
+
 class TestSceneMapErrors:
     def test_map_missing_scene(self, session: GodotIQSession) -> None:
         """Missing scene returns error dict."""
