DeltaZN
feat: rename god -> world
c58d3eb
Raw
History Blame Contribute Delete
5.24 kB
"""Deterministic autopilot for social (non-survival) worlds.
The autopilot fills gaps: it produces one safe, goal-consistent primitive when
an NPC has no usable directive (LLM error, unknown action, omitted NPC). It is
a fallback, never a filter — it does not constrain what a planner may choose.
"""
from __future__ import annotations
from world_simulator.domain import CitizenGoal, Npc, WorldState
from world_simulator.simulation.connectors.base import NpcDirective
from world_simulator.simulation.mechanics import (
ATTACK_RADIUS,
distance_between,
is_alive,
walk_away_target,
)
from world_simulator.simulation.perception import CitizenPerception, nearby_threat_npc
def autopilot_directive(
world: WorldState,
npc: Npc,
directive: NpcDirective,
perception: CitizenPerception,
goal: CitizenGoal | None,
) -> NpcDirective:
target = target_from_directive_or_goal(world, npc, directive, goal, perception)
if (
goal is not None
and goal.goal_type in ("pursue_and_attack_target", "retaliate")
and target is not None
):
return strike_or_approach_target(
npc,
directive,
target,
memory="Autopilot pursued the hostile target.",
)
if goal is not None and goal.goal_type in ("survive", "call_for_help", "help_or_defend"):
if goal.goal_type == "call_for_help":
return NpcDirective(
npc_id=npc.id,
action="speak",
message="Help! I am in danger!",
memory="Autopilot called for help.",
communication_intent="help_request",
intent="call_for_help",
)
if target is not None:
return flee_directive(world, npc, target, directive)
return idle_directive(
directive,
memory="Autopilot held a defensive stance.",
intent="defend",
)
if (
goal is not None
and goal.goal_type in ("respond_to_event", "investigate")
and target is not None
):
return move_toward_target_directive(npc, directive, target, intent="investigate")
return idle_directive(directive, memory=None, intent="observe")
def target_from_directive_or_goal(
world: WorldState,
npc: Npc,
directive: NpcDirective,
goal: CitizenGoal | None,
perception: CitizenPerception,
) -> Npc | None:
if directive.target_npc_id:
target = _npc_by_id(world, directive.target_npc_id)
if target is not None and target.id != npc.id and is_alive(target):
return target
if goal is not None and goal.target_npc_id:
target = _npc_by_id(world, goal.target_npc_id)
if target is not None and target.id != npc.id and is_alive(target):
return target
threat = nearby_threat_npc(world, perception)
if threat is not None and threat.id != npc.id and is_alive(threat):
return threat
return None
def strike_or_approach_target(
npc: Npc,
directive: NpcDirective,
target: Npc,
*,
memory: str,
) -> NpcDirective:
if distance_between(npc, target) <= ATTACK_RADIUS:
return NpcDirective(
npc_id=npc.id,
action="attack",
target_npc_id=target.id,
message=directive.message,
memory=memory,
intent="hostile_attack",
confidence=directive.confidence,
)
return move_toward_target_directive(
npc,
directive,
target,
intent="approach_target_for_attack",
memory=memory,
)
def move_toward_target_directive(
npc: Npc,
directive: NpcDirective,
target: Npc,
*,
intent: str,
memory: str | None = None,
) -> NpcDirective:
return NpcDirective(
npc_id=npc.id,
action="move",
target=target.position,
target_npc_id=target.id,
message=directive.message,
memory=memory or f"You moved toward {target.name}.",
intent=intent,
confidence=directive.confidence,
)
def flee_directive(
world: WorldState,
npc: Npc,
threat: Npc,
directive: NpcDirective,
) -> NpcDirective:
half_width = world.terrain.width / 2
half_depth = world.terrain.depth / 2
return NpcDirective(
npc_id=npc.id,
action="move",
target=walk_away_target(npc, threat, half_width=half_width, half_depth=half_depth),
target_npc_id=threat.id,
message=directive.message,
memory=f"You backed away from {threat.name}.",
intent="flee_from_threat",
confidence=directive.confidence,
)
def idle_directive(
directive: NpcDirective,
*,
memory: str | None,
intent: str,
) -> NpcDirective:
return NpcDirective(
npc_id=directive.npc_id,
action="idle",
target=None,
target_npc_id=directive.target_npc_id,
message=directive.message,
memory=memory,
intent=intent,
confidence=directive.confidence,
)
def _npc_by_id(world: WorldState, npc_id: str) -> Npc | None:
return next((candidate for candidate in world.living_npcs() if candidate.id == npc_id), None)