from __future__ import annotations from typing import Any, cast from world_simulator.domain import Npc, WorldLogEvent, WorldState from world_simulator.observability import append_record from world_simulator.simulation.mechanics import is_alive DEFAULT_DIRECTIVE_TTL_TICKS = 24 def set_active_directive( npc: Npc, directive: str, *, source: str, tick: int, ttl_ticks: int = DEFAULT_DIRECTIVE_TTL_TICKS, ) -> None: npc.god_directive = directive npc.directive_source = source npc.directive_issued_tick = tick npc.directive_ttl_ticks = ttl_ticks def clear_active_directive(npc: Npc) -> None: npc.god_directive = None npc.directive_source = None npc.directive_issued_tick = None npc.directive_ttl_ticks = None def directive_remaining_ttl(npc: Npc, *, tick: int) -> int | None: if npc.god_directive is None: return None if npc.directive_issued_tick is None or npc.directive_ttl_ticks is None: return DEFAULT_DIRECTIVE_TTL_TICKS return max(0, npc.directive_issued_tick + npc.directive_ttl_ticks - tick) def active_directive_payload(npc: Npc, *, tick: int) -> dict[str, Any] | None: if npc.god_directive is None: return None return { "text": npc.god_directive, "source": npc.directive_source or "unknown", "issued_tick": npc.directive_issued_tick, "ttl_ticks": npc.directive_ttl_ticks or DEFAULT_DIRECTIVE_TTL_TICKS, "remaining_ttl": directive_remaining_ttl(npc, tick=tick), } def expire_directives(world: WorldState, *, tick: int) -> list[dict[str, Any]]: expired: list[dict[str, Any]] = [] for npc in world.living_npcs(): remaining = directive_remaining_ttl(npc, tick=tick) if remaining is None or remaining > 0: continue record = { "npc_id": npc.id, "directive": npc.god_directive, "source": npc.directive_source, "issued_tick": npc.directive_issued_tick, "ttl_ticks": npc.directive_ttl_ticks, } clear_active_directive(npc) world.event_log.append( WorldLogEvent( tick=tick, type="directive_expired", actor_id="engine", target_id=npc.id, object_id=None, summary=f"Directive expired for {npc.name}", severity=cast(Any, "neutral"), ) ) append_record({"tick": tick, "phase": "directive_expired", **record}) expired.append(record) for npc in world.npcs: if not is_alive(npc): clear_active_directive(npc) return expired