get_legal_actions
Retrieve legal actions for your unit: movable tiles, attackable enemies, healable allies, and wait option. Works only on READY or MOVED status units. Call before issuing move, attack, heal, or wait to avoid errors.
Instructions
Read-only. Return all legal actions for one of your units this turn: movable tiles, attackable enemy unit_ids, healable ally unit_ids, and whether wait is available. Only works on your own units in READY or MOVED status; returns an error for enemy units or units that have already acted. unit_id is the string identifier from get_state. Call this before issuing move, attack, heal, or wait to avoid illegal-action errors.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| connection_id | Yes | ||
| unit_id | Yes |
Implementation Reference
- Tool handler that validates the session is active and the unit belongs to the viewer, then delegates to legal_actions_for_unit. Wraps IllegalAction into ToolError.
def get_legal_actions(session: Session, viewer: Team, unit_id: str) -> dict: _require_active(session, viewer) _require_own_unit(session.state, unit_id, viewer) try: return legal_actions_for_unit(session.state, unit_id) except IllegalAction as e: raise ToolError(str(e)) from e - Registration of get_legal_actions in the TOOL_REGISTRY with its input schema (requires unit_id string).
"get_legal_actions": { "fn": get_legal_actions, "description": "Get the legal moves/attacks/heals/wait for one of your units.", "input_schema": { "type": "object", "properties": {"unit_id": {"type": "string"}}, "required": ["unit_id"], }, }, - src/silicon_pantheon/server/tools/__init__.py:24-33 (registration)Import of get_legal_actions from read_only module into the tools package.
from .read_only import ( get_state, get_unit, get_unit_range, get_legal_actions, simulate_attack, get_threat_map, get_tactical_summary, get_history, ) - Core logic: computes legal moves, attacks, and heals for a unit based on its status (READY/MOVED/DONE), reachable tiles, attack range, and heal adjacency.
def legal_actions_for_unit(state: GameState, unit_id: str) -> dict: """Return the structured set of actions this unit can take right now. Keys: - status: current unit status - moves: list of {dest, cost} if unit can still move, else [] - attacks: list of {target_id, from, damage, will_counter, counter_damage, kills, counter_kills} reachable from current pos PLUS from each reachable destination if the unit hasn't moved - heals: similar, only if unit.can_heal - can_wait: True unless the unit is already DONE """ unit = state.units.get(unit_id) if unit is None or not unit.alive: raise IllegalAction(f"unit {unit_id} not found (dead, nonexistent, or hidden by fog)") if unit.owner is not state.active_player: raise IllegalAction(f"unit {unit_id} belongs to {unit.owner}, not active player") moves: list[dict] = [] attacks: list[dict] = [] heals: list[dict] = [] if unit.status is UnitStatus.READY: reach = reachable_tiles(state, unit) moves = [ {"dest": p.to_dict(), "cost": c} for p, c in sorted(reach.items(), key=lambda kv: (kv[1], kv[0].x, kv[0].y)) if p != unit.pos ] origins = list(reach.keys()) elif unit.status is UnitStatus.MOVED: origins = [unit.pos] else: # DONE return { "unit_id": unit_id, "status": unit.status.value, "moves": [], "attacks": [], "heals": [], "can_wait": False, } # Enumerate attacks and heals from every valid origin tile. for origin in origins: # Attacks for enemy in state.units.values(): if not enemy.alive or enemy.owner is unit.owner: continue if not in_attack_range(origin, enemy.pos, unit.stats): continue pred = predict_attack( unit, enemy, attacker_tile=state.board.tile(origin), defender_tile=state.board.tile(enemy.pos), attacker_pos=origin, ) attacks.append( { "target_id": enemy.id, "from": origin.to_dict(), "damage": pred.total_damage_to_defender, "kills": pred.defender_dies, "will_counter": pred.will_counter, "counter_damage": pred.total_counter_damage, "counter_kills": pred.attacker_dies, } ) # Heals if unit.stats.can_heal: for ally in state.units.values(): if not ally.alive or ally.owner is not unit.owner or ally.id == unit.id: continue if origin.manhattan(ally.pos) != 1: continue if ally.hp >= ally.stats.hp_max: continue # no effect; exclude heal_amt = min(unit.stats.heal_amount, ally.stats.hp_max - ally.hp) heals.append( { "target_id": ally.id, "from": origin.to_dict(), "heal_amount": heal_amt, } ) return { "unit_id": unit_id, "status": unit.status.value, "moves": moves, "attacks": attacks, "heals": heals, "can_wait": True, }