Skip to main content
Glama

get_unit_range

Retrieve a unit's full threat zone, including reachable movement tiles and attack tiles from any position. Use to plan positioning or evaluate enemy threat coverage for a specific unit.

Instructions

Read-only. Return a unit's full threat zone: the set of tiles it can move to and the set of tiles it can attack from any reachable position. Works for any alive unit, own or enemy. unit_id is the string identifier from get_state (e.g. 'red_cavalry_2'). Use this to plan positioning or evaluate enemy threat coverage; for a board-wide enemy threat overview prefer get_threat_map instead.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
connection_idYes
unit_idYes

Implementation Reference

  • The main handler function for get_unit_range. Computes move_tiles (BFS reachable positions) and attack_tiles (tiles attackable from any reachable position). Handles fog-of-war checks so enemy units hidden by fog are not queryable.
    def get_unit_range(session: Session, viewer: Team, unit_id: str) -> dict:
        """Return the full threat zone for a unit: tiles it can move to
        (BFS reachable set) AND tiles it can attack from any reachable
        position (the outer threat ring). Read-only, works for ANY alive
        unit (own or enemy), no turn-ownership check.
    
        Units with status DONE return empty sets (they can't act this
        turn -- nothing to show).
        """
        from ..engine.board import reachable_tiles, tiles_in_attack_range
    
        state = session.state
        u = state.units.get(unit_id)
        if u is None or not u.alive:
            raise ToolError(f"unit {unit_id} not found (dead, nonexistent, or hidden by fog)")
        # Fog check: enemy units hidden by fog must not be queryable.
        if u.owner != viewer:
            visible_enemies = _visible_enemies(session, viewer)
            if u not in visible_enemies:
                raise ToolError(f"unit {unit_id} not found (dead, nonexistent, or hidden by fog)")
    
        # Always show full hypothetical range from the unit's current
        # tile, regardless of status (ready/moved/done). This is a
        # visualization aid — "what could this unit reach?" — not tied
        # to whether it can actually act this turn.
        reach = reachable_tiles(state, u)
        move_set = set(reach.keys())
        move_tiles = [{"x": p.x, "y": p.y} for p in sorted(move_set, key=lambda p: (p.y, p.x))]
    
        # Attack range -- expand each reachable tile by the unit's
        # attack range, subtract the move set itself. This is the
        # "outer ring" of threat: tiles the unit can hit if it moves
        # optimally first. Current position is included in reach so
        # standing attacks are covered.
        attack_set: set[Pos] = set()
        for p in move_set:
            for t in tiles_in_attack_range(p, u.stats, state.board):
                if t not in move_set:
                    attack_set.add(t)
        attack_tiles = [{"x": p.x, "y": p.y} for p in sorted(attack_set, key=lambda p: (p.y, p.x))]
    
        return {
            "unit_id": unit_id,
            "move_tiles": move_tiles,
            "attack_tiles": attack_tiles,
        }
  • Registration of get_unit_range in TOOL_REGISTRY with description and input_schema (requires unit_id string).
    "get_unit_range": {
        "fn": get_unit_range,
        "description": (
            "Full threat zone for a unit: tiles it can move to (BFS "
            "reachable) + tiles it can attack from any reachable "
            "position (the outer threat ring). Works for any alive "
            "unit, own or enemy. Units with status=done return empty."
        ),
        "input_schema": {
            "type": "object",
            "properties": {"unit_id": {"type": "string"}},
            "required": ["unit_id"],
        },
    },
  • Import of get_unit_range from read_only module into the tools package for re-export.
        get_unit_range,
        get_legal_actions,
        simulate_attack,
        get_threat_map,
        get_tactical_summary,
        get_history,
    )
  • reachable_tiles helper – performs Dijkstra/BFS to find all tiles a unit can reach within its move budget, respecting terrain, enemy blocking, and ally blocking.
    def reachable_tiles(state: GameState, unit: Unit) -> dict[Pos, int]:
        """Return {destination: cost} for every tile reachable within `unit.stats.move`.
    
        Rules:
        - Cannot cross tiles occupied by enemy units.
        - Can pass through tiles occupied by allied units but cannot end there.
        - Cannot enter terrain the unit's class forbids.
        - Unit's current tile is included with cost 0.
        """
        board = state.board
        stats = unit.stats
        start = unit.pos
    
        # Dijkstra: each tile's cost is paid on entry (so starting tile is 0).
        dist: dict[Pos, int] = {start: 0}
        pq: list[tuple[int, int, int]] = [(0, start.x, start.y)]
    
        while pq:
            d, x, y = heapq.heappop(pq)
            p = Pos(x, y)
            if d > dist[p]:
                continue
            for n in p.neighbors4():
                if not board.in_bounds(n):
                    continue
                tile = board.tile(n)
                if not can_enter(stats, tile, unit.class_):
                    continue
                occupant = state.unit_at(n)
                if occupant is not None and occupant.owner is not unit.owner:
                    # cannot pass through enemies
                    continue
                step = tile.move_cost(unit.class_)
                nd = d + step
                if nd > stats.move:
                    continue
                if nd < dist.get(n, 10**9):
                    dist[n] = nd
                    heapq.heappush(pq, (nd, n.x, n.y))
    
        # Filter out tiles blocked by allies (cannot end there), keep starting tile.
        result: dict[Pos, int] = {}
        for p, d in dist.items():
            if p == start:
                result[p] = d
                continue
            occupant = state.unit_at(p)
            if occupant is not None:
                continue  # ally or enemy — can't end here
            result[p] = d
        return result
  • tiles_in_attack_range helper – returns all in-bounds tiles at Manhattan distance within the unit's attack range (rng_min to rng_max) from a given position.
    def tiles_in_attack_range(pos: Pos, stats: UnitStats, board: Board) -> list[Pos]:
        """All in-bounds tiles at Manhattan distance within the unit's attack range."""
        out: list[Pos] = []
        for dx in range(-stats.rng_max, stats.rng_max + 1):
            for dy in range(-stats.rng_max, stats.rng_max + 1):
                d = abs(dx) + abs(dy)
                if d < stats.rng_min or d > stats.rng_max:
                    continue
                p = Pos(pos.x + dx, pos.y + dy)
                if board.in_bounds(p):
                    out.append(p)
        return out
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden. Starts with 'Read-only' indicating non-destructive operation. States works for any alive unit, own or enemy, clarifying ownership constraints. Lacks details on error handling or rate limits, but adequately covers key behavioral traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with 'Read-only,' no wasted words. Efficiently conveys purpose, usage, and alternative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema and two parameters, the description covers behavior, input format, and usage guidance. Lacks mention of error conditions or what happens with invalid inputs, but is sufficient for basic use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so description must compensate. It explains unit_id with an example ('red_cavalry_2') but does not explain connection_id at all. With only two parameters and one left undocumented, the description does not fully compensate for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the tool returns a unit's full threat zone (move and attack tiles) and specifies it works for any alive unit, own or enemy. It distinguishes from sibling tool get_threat_map by noting the difference between single-unit and board-wide overview.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells when to use (plan positioning or evaluate enemy threat coverage) and when to prefer alternative (get_threat_map for board-wide overview). Also provides unit_id format example.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/haoyifan/Silicon-Pantheon'

If you have feedback or need assistance with the MCP directory API, please join our Discord server