feat: Implement Spatial Graphs as Signal Processing Layer

This commit is contained in:
2026-04-12 11:55:25 +05:30
parent 079fe3ff22
commit 3902999f60
6 changed files with 814 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ from langchain_core.messages import HumanMessage, SystemMessage
from entities import Entity
from llm_runtime import _format_prompt, _normalize_llm_output, llm
from spatial_graph import SpatialGraph
from time_utils import WorldClock, describe_relative_time
from world_architect import invoke_architect, apply_state_delta, WorldState
@@ -17,6 +18,7 @@ def ask_entity(
world_clock: WorldClock,
location: str,
world_state: WorldState | None = None,
spatial_graph: SpatialGraph | None = None,
):
facts = entity.memory.retrieve(
player_query,
@@ -103,8 +105,90 @@ RECENT CHAT: {recent_context}
if state_delta:
logger.info("Applying state delta to world...")
apply_state_delta(world_state, state_delta)
logger.info(
"World time now: %s", world_state.world_clock.get_time_str()
)
logger.info("World time now: %s", world_state.world_clock.get_time_str())
else:
logger.info("No state changes from architect")
# Broadcast action through spatial graph
if spatial_graph:
logger.info("Broadcasting action through spatial graph...")
entity_pos = spatial_graph.get_entity_position(entity.entity_id)
if entity_pos:
# Get all perceiving entities
perceptions = spatial_graph.bubble_up_broadcast(
location_id=entity_pos.location_id,
action=response,
actor_id=entity.entity_id,
llm_filter=_portal_filter_llm,
escalation_check=_escalation_check_llm,
)
# Update other entities' memory based on perceptions
logger.info(f"Perception broadcast: {len(perceptions)} entities perceiving")
for perceiver_id, perception in perceptions.items():
if perceiver_id == entity.entity_id:
continue
# Find perceiving entity in current session (would be in entities dict)
if perception.perceivable:
logger.debug(
f"{perceiver_id} perceives: {perception.transformed_action}"
)
else:
logger.warning(f"Entity {entity.entity_id} has no spatial position")
else:
logger.debug("No spatial graph provided, skipping perception broadcast")
def _portal_filter_llm(action, source_id, target_id, portal_conn):
"""
Simple portal filtering based on connection properties.
Can be enhanced with actual LLM calls if needed.
"""
vision = portal_conn.vision_prop
sound = portal_conn.sound_prop
if vision < 1 and sound < 1:
return None
if vision < 2 or sound < 2:
return f"You hear muffled sounds from {source_id}."
if vision < 5 or sound < 5:
words = action.split()
if len(words) > 2:
return f"You hear indistinct sounds from {source_id}..."
return f"You hear from {source_id}: {action}"
# Clear
return action
def _escalation_check_llm(action, source_id, parent_id):
"""
Simple escalation check based on keywords.
Can be enhanced with actual LLM calls if needed.
"""
escalation_keywords = [
"yell",
"scream",
"shout",
"cry",
"crash",
"bang",
"explosion",
"smash",
"break",
"attack",
"fight",
"combat",
"blood",
"murder",
"help",
"alarm",
"emergency",
]
action_lower = action.lower()
return any(kw in action_lower for kw in escalation_keywords)