cogames-agents 0.0.0.7__cp312-cp312-macosx_11_0_arm64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cogames_agents/__init__.py +0 -0
- cogames_agents/evals/__init__.py +5 -0
- cogames_agents/evals/planky_evals.py +415 -0
- cogames_agents/policy/__init__.py +0 -0
- cogames_agents/policy/evolution/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
- cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
- cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
- cogames_agents/policy/nim_agents/__init__.py +20 -0
- cogames_agents/policy/nim_agents/agents.py +98 -0
- cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
- cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
- cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
- cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
- cogames_agents/policy/nim_agents/common.nim +1054 -0
- cogames_agents/policy/nim_agents/install.sh +1 -0
- cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
- cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
- cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
- cogames_agents/policy/nim_agents/nimby.lock +3 -0
- cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
- cogames_agents/policy/nim_agents/random_agents.nim +68 -0
- cogames_agents/policy/nim_agents/test_agents.py +53 -0
- cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
- cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
- cogames_agents/policy/scripted_agent/README.md +360 -0
- cogames_agents/policy/scripted_agent/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
- cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
- cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
- cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
- cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
- cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
- cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
- cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
- cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
- cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
- cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
- cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
- cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
- cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
- cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
- cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
- cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
- cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
- cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
- cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
- cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
- cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
- cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
- cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
- cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
- cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
- cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
- cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
- cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
- cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
- cogames_agents/policy/scripted_agent/common/roles.py +34 -0
- cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
- cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
- cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
- cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
- cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
- cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
- cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
- cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
- cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
- cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
- cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
- cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
- cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
- cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
- cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
- cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
- cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
- cogames_agents/policy/scripted_agent/planky/README.md +214 -0
- cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
- cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
- cogames_agents/policy/scripted_agent/planky/context.py +68 -0
- cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
- cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
- cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
- cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
- cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
- cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
- cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
- cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
- cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
- cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
- cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
- cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
- cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
- cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
- cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
- cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
- cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
- cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
- cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
- cogames_agents/policy/scripted_agent/types.py +239 -0
- cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
- cogames_agents/policy/scripted_agent/utils.py +381 -0
- cogames_agents/policy/scripted_registry.py +80 -0
- cogames_agents/py.typed +0 -0
- cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
- cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
- cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
- cogames_agents-0.0.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Debug harness for diagnosing CoGsGuard agent behaviors.
|
|
3
|
+
|
|
4
|
+
This module provides tools to:
|
|
5
|
+
- Step through simulation manually
|
|
6
|
+
- Inspect agent state, observations, and internal policy state
|
|
7
|
+
- Compare actual simulation state vs internal agent tracking
|
|
8
|
+
- Identify coordinate mismatches, stuck agents, and other issues
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from cogames_agents.policy.scripted_agent.cogsguard.debug_agent import DebugHarness
|
|
12
|
+
|
|
13
|
+
harness = DebugHarness.from_recipe("recipes.experiment.cogsguard")
|
|
14
|
+
harness.step(10) # Run 10 steps
|
|
15
|
+
harness.print_agent_summary()
|
|
16
|
+
harness.diagnose_stuck_agents()
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import sys
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Any, Callable
|
|
22
|
+
|
|
23
|
+
from cogames_agents.policy.scripted_agent.cogsguard.types import StructureType
|
|
24
|
+
|
|
25
|
+
# Ensure we can import from project root
|
|
26
|
+
sys.path.insert(0, ".")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class AgentDebugInfo:
|
|
31
|
+
"""Debug information for a single agent."""
|
|
32
|
+
|
|
33
|
+
agent_id: int
|
|
34
|
+
# Internal state (from policy)
|
|
35
|
+
internal_pos: tuple[int, int] | None = None
|
|
36
|
+
role: str | None = None
|
|
37
|
+
phase: str | None = None
|
|
38
|
+
cargo: int = 0
|
|
39
|
+
cargo_capacity: int = 4
|
|
40
|
+
has_gear: bool = False
|
|
41
|
+
current_vibe: str | None = None
|
|
42
|
+
last_action: str | None = None
|
|
43
|
+
# Actual state (from simulation)
|
|
44
|
+
actual_inventory: dict[str, int] = field(default_factory=dict)
|
|
45
|
+
# Navigation
|
|
46
|
+
target_position: tuple[int, int] | None = None
|
|
47
|
+
hub_pos: tuple[int, int] | None = None
|
|
48
|
+
# History
|
|
49
|
+
position_history: list[tuple[int, int]] = field(default_factory=list)
|
|
50
|
+
stuck_count: int = 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DebugHarness:
|
|
54
|
+
"""Debug harness for CoGsGuard agent diagnostics."""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
env_cfg: Any,
|
|
59
|
+
policy: Any,
|
|
60
|
+
agent_policies: list[Any],
|
|
61
|
+
rollout: Any,
|
|
62
|
+
):
|
|
63
|
+
self.env_cfg = env_cfg
|
|
64
|
+
self.policy = policy
|
|
65
|
+
self.agent_policies = agent_policies
|
|
66
|
+
self.rollout = rollout
|
|
67
|
+
self.sim = rollout._sim
|
|
68
|
+
self.agents = rollout._agents
|
|
69
|
+
self.num_agents = len(self.agents)
|
|
70
|
+
self.step_count = 0
|
|
71
|
+
|
|
72
|
+
# Track debug info per agent
|
|
73
|
+
self.agent_info: dict[int, AgentDebugInfo] = {i: AgentDebugInfo(agent_id=i) for i in range(self.num_agents)}
|
|
74
|
+
|
|
75
|
+
# Callback hooks
|
|
76
|
+
self.on_step_callbacks: list[Callable[["DebugHarness"], None]] = []
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def from_recipe(
|
|
80
|
+
cls,
|
|
81
|
+
recipe_module: str = "recipes.experiment.cogsguard",
|
|
82
|
+
num_agents: int = 10,
|
|
83
|
+
max_steps: int = 1000,
|
|
84
|
+
seed: int = 42,
|
|
85
|
+
policy_uri: str = "metta://policy/role?scrambler=1&miner=4",
|
|
86
|
+
) -> "DebugHarness":
|
|
87
|
+
"""Create debug harness from a recipe module.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
recipe_module: Module path to recipe (e.g., "recipes.experiment.cogsguard")
|
|
91
|
+
num_agents: Number of agents
|
|
92
|
+
max_steps: Maximum simulation steps
|
|
93
|
+
seed: Random seed
|
|
94
|
+
policy_uri: Policy URI with role counts (e.g., "metta://policy/role?miner=4&scrambler=1")
|
|
95
|
+
"""
|
|
96
|
+
import importlib
|
|
97
|
+
|
|
98
|
+
from mettagrid.policy.loader import initialize_or_load_policy
|
|
99
|
+
from mettagrid.policy.policy_env_interface import PolicyEnvInterface
|
|
100
|
+
from mettagrid.simulator.rollout import Rollout
|
|
101
|
+
from mettagrid.util.uri_resolvers.schemes import policy_spec_from_uri
|
|
102
|
+
|
|
103
|
+
# Import recipe and get make_env
|
|
104
|
+
recipe = importlib.import_module(recipe_module)
|
|
105
|
+
make_env = recipe.make_env
|
|
106
|
+
|
|
107
|
+
# Create environment config
|
|
108
|
+
env_cfg = make_env(num_agents=num_agents, max_steps=max_steps)
|
|
109
|
+
policy_env_info = PolicyEnvInterface.from_mg_cfg(env_cfg)
|
|
110
|
+
|
|
111
|
+
# Load the role policy with role counts from URI
|
|
112
|
+
policy_spec = policy_spec_from_uri(policy_uri)
|
|
113
|
+
multi_policy = initialize_or_load_policy(policy_env_info, policy_spec)
|
|
114
|
+
|
|
115
|
+
# Create per-agent policies
|
|
116
|
+
agent_policies = [multi_policy.agent_policy(i) for i in range(num_agents)]
|
|
117
|
+
|
|
118
|
+
# Create rollout
|
|
119
|
+
rollout = Rollout(
|
|
120
|
+
config=env_cfg,
|
|
121
|
+
policies=agent_policies,
|
|
122
|
+
render_mode=None,
|
|
123
|
+
seed=seed,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return cls(env_cfg, multi_policy, agent_policies, rollout)
|
|
127
|
+
|
|
128
|
+
def get_agent_state(self, agent_idx: int) -> Any | None:
|
|
129
|
+
"""Get internal policy state for an agent."""
|
|
130
|
+
agent_policy = self.agent_policies[agent_idx]
|
|
131
|
+
if hasattr(agent_policy, "_state"):
|
|
132
|
+
return agent_policy._state
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def step(self, n: int = 1) -> None:
|
|
136
|
+
"""Execute n simulation steps."""
|
|
137
|
+
for _ in range(n):
|
|
138
|
+
self.rollout.step()
|
|
139
|
+
self.step_count += 1
|
|
140
|
+
self._update_debug_info()
|
|
141
|
+
for callback in self.on_step_callbacks:
|
|
142
|
+
callback(self)
|
|
143
|
+
|
|
144
|
+
def _update_debug_info(self) -> None:
|
|
145
|
+
"""Update debug info for all agents after a step."""
|
|
146
|
+
for i in range(self.num_agents):
|
|
147
|
+
info = self.agent_info[i]
|
|
148
|
+
state = self.get_agent_state(i)
|
|
149
|
+
|
|
150
|
+
if state:
|
|
151
|
+
pos = (state.row, state.col)
|
|
152
|
+
info.internal_pos = pos
|
|
153
|
+
info.role = state.role.value if hasattr(state.role, "value") else str(state.role)
|
|
154
|
+
info.phase = state.phase.value if hasattr(state.phase, "value") else str(state.phase)
|
|
155
|
+
info.cargo = state.total_cargo
|
|
156
|
+
info.cargo_capacity = state.cargo_capacity
|
|
157
|
+
info.has_gear = state.has_gear()
|
|
158
|
+
info.current_vibe = state.current_vibe
|
|
159
|
+
info.last_action = state.last_action.name if state.last_action else None
|
|
160
|
+
info.target_position = state.target_position
|
|
161
|
+
info.hub_pos = state.get_structure_position(StructureType.HUB)
|
|
162
|
+
|
|
163
|
+
# Track position history
|
|
164
|
+
info.position_history.append(pos)
|
|
165
|
+
if len(info.position_history) > 30:
|
|
166
|
+
info.position_history.pop(0)
|
|
167
|
+
|
|
168
|
+
# Detect stuck
|
|
169
|
+
if len(info.position_history) >= 10:
|
|
170
|
+
recent = info.position_history[-10:]
|
|
171
|
+
if all(p == recent[0] for p in recent):
|
|
172
|
+
info.stuck_count += 1
|
|
173
|
+
else:
|
|
174
|
+
info.stuck_count = 0
|
|
175
|
+
|
|
176
|
+
# Get actual inventory from simulation
|
|
177
|
+
info.actual_inventory = dict(self.agents[i].inventory)
|
|
178
|
+
|
|
179
|
+
def get_actual_agent_position(self, agent_idx: int) -> tuple[int, int] | None:
|
|
180
|
+
"""Get actual agent position from simulation.
|
|
181
|
+
|
|
182
|
+
Returns (row, col) in simulation coordinates.
|
|
183
|
+
"""
|
|
184
|
+
agent = self.agents[agent_idx]
|
|
185
|
+
if hasattr(agent, "location"):
|
|
186
|
+
loc = agent.location
|
|
187
|
+
# location is typically (col, row) so convert to (row, col)
|
|
188
|
+
return (loc[1], loc[0])
|
|
189
|
+
elif hasattr(agent, "r") and hasattr(agent, "c"):
|
|
190
|
+
return (agent.r, agent.c)
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def verify_position_tracking(self, verbose: bool = True) -> dict[int, dict]:
|
|
194
|
+
"""Verify that internal position tracking matches simulation.
|
|
195
|
+
|
|
196
|
+
Since internal positions are RELATIVE to starting position,
|
|
197
|
+
we track deltas from the starting position and compare movement.
|
|
198
|
+
|
|
199
|
+
Returns dict of agent_id -> {issues found}
|
|
200
|
+
"""
|
|
201
|
+
results: dict[int, dict] = {}
|
|
202
|
+
|
|
203
|
+
if verbose:
|
|
204
|
+
print(f"\n=== Position Tracking Verification (step {self.step_count}) ===")
|
|
205
|
+
|
|
206
|
+
for i in range(self.num_agents):
|
|
207
|
+
state = self.get_agent_state(i)
|
|
208
|
+
if not state:
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
# Get internal position (relative coords, centered at ~100)
|
|
212
|
+
internal_pos = (state.row, state.col)
|
|
213
|
+
|
|
214
|
+
# Get actual simulation position
|
|
215
|
+
actual_pos = self.get_actual_agent_position(i)
|
|
216
|
+
|
|
217
|
+
# Get what action was intended vs executed
|
|
218
|
+
intended = state.last_action.name if state.last_action else "none"
|
|
219
|
+
executed = state.last_action_executed if hasattr(state, "last_action_executed") else "unknown"
|
|
220
|
+
|
|
221
|
+
agent_result = {
|
|
222
|
+
"internal_pos": internal_pos,
|
|
223
|
+
"actual_pos": actual_pos,
|
|
224
|
+
"intended_action": intended,
|
|
225
|
+
"executed_action": executed,
|
|
226
|
+
"mismatch": intended != executed if executed != "unknown" else None,
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# Check if internal position is within expected grid bounds
|
|
230
|
+
# Internal coords centered at ~100, so valid range is roughly 0-200
|
|
231
|
+
if internal_pos[0] < 0 or internal_pos[0] >= 200 or internal_pos[1] < 0 or internal_pos[1] >= 200:
|
|
232
|
+
agent_result["out_of_bounds"] = True
|
|
233
|
+
|
|
234
|
+
results[i] = agent_result
|
|
235
|
+
|
|
236
|
+
if verbose:
|
|
237
|
+
mismatch_str = ""
|
|
238
|
+
if agent_result.get("mismatch"):
|
|
239
|
+
mismatch_str = f" [MISMATCH: intended={intended}, executed={executed}]"
|
|
240
|
+
print(
|
|
241
|
+
f"Agent {i}: internal={internal_pos}, actual_sim={actual_pos}, last_action={intended}{mismatch_str}"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
|
|
246
|
+
def track_position_drift(self, num_steps: int = 50, verbose: bool = True) -> None:
|
|
247
|
+
"""Run simulation and track if positions drift from expected.
|
|
248
|
+
|
|
249
|
+
This helps identify if internal position tracking diverges from reality.
|
|
250
|
+
"""
|
|
251
|
+
if verbose:
|
|
252
|
+
print(f"\n=== Position Drift Tracking ({num_steps} steps) ===")
|
|
253
|
+
|
|
254
|
+
# Record starting positions for each agent
|
|
255
|
+
start_internal: dict[int, tuple[int, int]] = {}
|
|
256
|
+
start_actual: dict[int, tuple[int, int] | None] = {}
|
|
257
|
+
|
|
258
|
+
for i in range(self.num_agents):
|
|
259
|
+
state = self.get_agent_state(i)
|
|
260
|
+
if state:
|
|
261
|
+
start_internal[i] = (state.row, state.col)
|
|
262
|
+
start_actual[i] = self.get_actual_agent_position(i)
|
|
263
|
+
|
|
264
|
+
# Track movement counts
|
|
265
|
+
move_counts: dict[int, dict[str, int]] = {i: {"intended": 0, "executed": 0} for i in range(self.num_agents)}
|
|
266
|
+
mismatches: dict[int, list[tuple[int, str, str]]] = {i: [] for i in range(self.num_agents)}
|
|
267
|
+
|
|
268
|
+
# Step through simulation
|
|
269
|
+
for _step in range(num_steps):
|
|
270
|
+
self.step(1)
|
|
271
|
+
|
|
272
|
+
for i in range(self.num_agents):
|
|
273
|
+
state = self.get_agent_state(i)
|
|
274
|
+
if not state:
|
|
275
|
+
continue
|
|
276
|
+
|
|
277
|
+
intended = state.last_action.name if state.last_action else "noop"
|
|
278
|
+
executed = getattr(state, "last_action_executed", None) or "unknown"
|
|
279
|
+
|
|
280
|
+
# Count moves
|
|
281
|
+
if intended.startswith("move_"):
|
|
282
|
+
move_counts[i]["intended"] += 1
|
|
283
|
+
if executed and executed.startswith("move_"):
|
|
284
|
+
move_counts[i]["executed"] += 1
|
|
285
|
+
|
|
286
|
+
# Track mismatches
|
|
287
|
+
if intended != executed and executed != "unknown":
|
|
288
|
+
mismatches[i].append((self.step_count, intended, executed))
|
|
289
|
+
|
|
290
|
+
# Report results
|
|
291
|
+
if verbose:
|
|
292
|
+
print("\n--- Results ---")
|
|
293
|
+
for i in range(self.num_agents):
|
|
294
|
+
state = self.get_agent_state(i)
|
|
295
|
+
if not state:
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
end_internal = (state.row, state.col)
|
|
299
|
+
|
|
300
|
+
# Calculate movement delta
|
|
301
|
+
internal_delta = (
|
|
302
|
+
end_internal[0] - start_internal[i][0],
|
|
303
|
+
end_internal[1] - start_internal[i][1],
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Note: actual_delta could be computed here if needed for debugging
|
|
307
|
+
# but we focus on internal tracking consistency
|
|
308
|
+
|
|
309
|
+
mismatch_count = len(mismatches[i])
|
|
310
|
+
moves = move_counts[i]
|
|
311
|
+
|
|
312
|
+
print(
|
|
313
|
+
f"Agent {i}: internal_delta={internal_delta}, "
|
|
314
|
+
f"moves(intended={moves['intended']}, executed={moves['executed']}), "
|
|
315
|
+
f"action_mismatches={mismatch_count}"
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if mismatch_count > 0 and verbose:
|
|
319
|
+
print(f" First 5 mismatches: {mismatches[i][:5]}")
|
|
320
|
+
|
|
321
|
+
def get_grid_objects(self) -> dict[int, dict]:
|
|
322
|
+
"""Get all grid objects from simulation."""
|
|
323
|
+
return self.sim.grid_objects()
|
|
324
|
+
|
|
325
|
+
def get_objects_by_type(self, type_name: str) -> list[dict]:
|
|
326
|
+
"""Get all objects of a specific type."""
|
|
327
|
+
return [obj for obj in self.get_grid_objects().values() if obj.get("type_name") == type_name]
|
|
328
|
+
|
|
329
|
+
def get_object_types(self) -> dict[str, int]:
|
|
330
|
+
"""Get count of each object type in simulation."""
|
|
331
|
+
types: dict[str, int] = {}
|
|
332
|
+
for obj in self.get_grid_objects().values():
|
|
333
|
+
t = obj.get("type_name", "unknown")
|
|
334
|
+
types[t] = types.get(t, 0) + 1
|
|
335
|
+
return dict(sorted(types.items()))
|
|
336
|
+
|
|
337
|
+
def find_hubs(self) -> list[tuple[int, int]]:
|
|
338
|
+
"""Find actual hub positions in simulation."""
|
|
339
|
+
positions = []
|
|
340
|
+
for obj in self.get_grid_objects().values():
|
|
341
|
+
type_name = obj.get("type_name", "")
|
|
342
|
+
if "hub" in type_name.lower() or "nexus" in type_name.lower():
|
|
343
|
+
# Use location tuple or r,c
|
|
344
|
+
loc = obj.get("location")
|
|
345
|
+
if loc:
|
|
346
|
+
positions.append((loc[1], loc[0])) # Convert (col, row) to (row, col)
|
|
347
|
+
else:
|
|
348
|
+
positions.append((obj.get("r", 0), obj.get("c", 0)))
|
|
349
|
+
return positions
|
|
350
|
+
|
|
351
|
+
def print_agent_summary(self, agent_ids: list[int] | None = None) -> None:
|
|
352
|
+
"""Print summary of agent states."""
|
|
353
|
+
if agent_ids is None:
|
|
354
|
+
agent_ids = list(range(self.num_agents))
|
|
355
|
+
|
|
356
|
+
print(f"\n=== Agent Summary (step {self.step_count}) ===")
|
|
357
|
+
for i in agent_ids:
|
|
358
|
+
info = self.agent_info[i]
|
|
359
|
+
gear_str = "GEAR" if info.has_gear else "NO_GEAR"
|
|
360
|
+
stuck_str = f" [STUCK x{info.stuck_count}]" if info.stuck_count > 0 else ""
|
|
361
|
+
print(
|
|
362
|
+
f"Agent {i}: pos={info.internal_pos} role={info.role} phase={info.phase} "
|
|
363
|
+
f"cargo={info.cargo}/{info.cargo_capacity} {gear_str}{stuck_str}"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
def print_simulation_info(self) -> None:
|
|
367
|
+
"""Print simulation state info."""
|
|
368
|
+
print(f"\n=== Simulation Info (step {self.step_count}) ===")
|
|
369
|
+
print(f"Object types: {self.get_object_types()}")
|
|
370
|
+
print(f"Hubs at: {self.find_hubs()}")
|
|
371
|
+
|
|
372
|
+
def diagnose_stuck_agents(self, threshold: int = 5) -> list[int]:
|
|
373
|
+
"""Find and diagnose stuck agents.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
threshold: Number of consecutive stuck steps to consider an agent stuck
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
List of stuck agent IDs
|
|
380
|
+
"""
|
|
381
|
+
stuck = []
|
|
382
|
+
for i in range(self.num_agents):
|
|
383
|
+
info = self.agent_info[i]
|
|
384
|
+
if info.stuck_count >= threshold:
|
|
385
|
+
stuck.append(i)
|
|
386
|
+
self._diagnose_agent(i)
|
|
387
|
+
return stuck
|
|
388
|
+
|
|
389
|
+
def _diagnose_agent(self, agent_id: int) -> None:
|
|
390
|
+
"""Print detailed diagnosis for an agent."""
|
|
391
|
+
info = self.agent_info[agent_id]
|
|
392
|
+
state = self.get_agent_state(agent_id)
|
|
393
|
+
|
|
394
|
+
print(f"\n=== STUCK AGENT {agent_id} (step {self.step_count}) ===")
|
|
395
|
+
print(f"Internal Position: {info.internal_pos}")
|
|
396
|
+
print(f"Role: {info.role}, Phase: {info.phase}")
|
|
397
|
+
print(f"Cargo: {info.cargo}/{info.cargo_capacity}, Gear: {info.has_gear}")
|
|
398
|
+
print(f"Current vibe: {info.current_vibe}")
|
|
399
|
+
print(f"Last action: {info.last_action}")
|
|
400
|
+
print(f"Actual inventory: {info.actual_inventory}")
|
|
401
|
+
|
|
402
|
+
if state:
|
|
403
|
+
print(f"\nStored hub location: {info.hub_pos}")
|
|
404
|
+
|
|
405
|
+
# Find actual hubs
|
|
406
|
+
actual_hubs = self.find_hubs()
|
|
407
|
+
print(f"Actual hubs in sim: {actual_hubs}")
|
|
408
|
+
|
|
409
|
+
# Check nearby structures from state
|
|
410
|
+
if info.internal_pos and hasattr(state, "structures"):
|
|
411
|
+
print("\nNearby structures (within 10 cells):")
|
|
412
|
+
for pos, struct in state.structures.items():
|
|
413
|
+
dist = abs(pos[0] - info.internal_pos[0]) + abs(pos[1] - info.internal_pos[1])
|
|
414
|
+
if dist <= 10:
|
|
415
|
+
stype = struct.structure_type
|
|
416
|
+
struct_type = stype.value if hasattr(stype, "value") else str(stype)
|
|
417
|
+
print(f" {struct_type} at {pos}: inv={struct.inventory_amount}, dist={dist}")
|
|
418
|
+
|
|
419
|
+
# Check agent occupancy
|
|
420
|
+
if hasattr(state, "agent_occupancy") and state.agent_occupancy:
|
|
421
|
+
print(f"\nAgent occupancy: {list(state.agent_occupancy)[:10]}")
|
|
422
|
+
|
|
423
|
+
print(f"\nRecent positions: {info.position_history[-10:]}")
|
|
424
|
+
|
|
425
|
+
def diagnose_coordinate_system(self) -> None:
|
|
426
|
+
"""Diagnose coordinate system issues.
|
|
427
|
+
|
|
428
|
+
NOTE: Internal coordinates are RELATIVE to each agent's starting position.
|
|
429
|
+
They will NOT match absolute simulation coordinates, and that's expected.
|
|
430
|
+
|
|
431
|
+
This diagnosis checks if the internal coordinate system is consistent:
|
|
432
|
+
- Are agents tracking their positions correctly?
|
|
433
|
+
- Can agents navigate to their believed hub position?
|
|
434
|
+
"""
|
|
435
|
+
print("\n=== Coordinate System Diagnosis ===")
|
|
436
|
+
print("Note: Internal coords are relative to starting position (centered at ~100,100)")
|
|
437
|
+
print(" Simulation coords are absolute (different coordinate system)")
|
|
438
|
+
|
|
439
|
+
# Get actual hub positions (for reference only)
|
|
440
|
+
actual_hubs = self.find_hubs()
|
|
441
|
+
print(f"\nActual hub in simulation (absolute coords): {actual_hubs}")
|
|
442
|
+
|
|
443
|
+
# Check what agents believe (internal coords)
|
|
444
|
+
print("\nAgent beliefs (internal relative coords):")
|
|
445
|
+
for i in range(self.num_agents):
|
|
446
|
+
info = self.agent_info[i]
|
|
447
|
+
if info.hub_pos:
|
|
448
|
+
if info.internal_pos:
|
|
449
|
+
dx = abs(info.internal_pos[0] - info.hub_pos[0])
|
|
450
|
+
dy = abs(info.internal_pos[1] - info.hub_pos[1])
|
|
451
|
+
dist_to_hub = dx + dy
|
|
452
|
+
else:
|
|
453
|
+
dist_to_hub = "?"
|
|
454
|
+
print(f" Agent {i}: at {info.internal_pos}, hub at {info.hub_pos}, dist={dist_to_hub}")
|
|
455
|
+
|
|
456
|
+
# Check for potential issues
|
|
457
|
+
print("\nConsistency check:")
|
|
458
|
+
issues = []
|
|
459
|
+
for i in range(self.num_agents):
|
|
460
|
+
info = self.agent_info[i]
|
|
461
|
+
state = self.get_agent_state(i)
|
|
462
|
+
|
|
463
|
+
if state and info.internal_pos:
|
|
464
|
+
# Check if agent has been stuck trying to deposit (full cargo, near hub)
|
|
465
|
+
if info.hub_pos and info.cargo >= info.cargo_capacity:
|
|
466
|
+
dx = abs(info.internal_pos[0] - info.hub_pos[0])
|
|
467
|
+
dy = abs(info.internal_pos[1] - info.hub_pos[1])
|
|
468
|
+
dist = dx + dy
|
|
469
|
+
if dist <= 2 and info.stuck_count > 20:
|
|
470
|
+
issues.append(i)
|
|
471
|
+
print(f" Agent {i}: STUCK near hub with full cargo! (dist={dist}, stuck={info.stuck_count})")
|
|
472
|
+
|
|
473
|
+
if issues:
|
|
474
|
+
print(f"\n*** POTENTIAL ISSUE: Agents {issues} stuck near hub ***")
|
|
475
|
+
print("This could indicate deposits are failing or navigation issues.")
|
|
476
|
+
else:
|
|
477
|
+
print(" No obvious consistency issues detected.")
|
|
478
|
+
|
|
479
|
+
def run_until(self, condition: Callable[["DebugHarness"], bool], max_steps: int = 1000) -> int:
|
|
480
|
+
"""Run simulation until condition is met or max_steps reached.
|
|
481
|
+
|
|
482
|
+
Args:
|
|
483
|
+
condition: Function that returns True when we should stop
|
|
484
|
+
max_steps: Maximum steps to run
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
Number of steps executed
|
|
488
|
+
"""
|
|
489
|
+
steps = 0
|
|
490
|
+
while steps < max_steps and not condition(self):
|
|
491
|
+
self.step()
|
|
492
|
+
steps += 1
|
|
493
|
+
return steps
|
|
494
|
+
|
|
495
|
+
def run_until_stuck(self, threshold: int = 10, max_steps: int = 500) -> list[int]:
|
|
496
|
+
"""Run until any agent is stuck for threshold steps.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
List of stuck agent IDs
|
|
500
|
+
"""
|
|
501
|
+
|
|
502
|
+
def any_stuck(h: DebugHarness) -> bool:
|
|
503
|
+
return any(info.stuck_count >= threshold for info in h.agent_info.values())
|
|
504
|
+
|
|
505
|
+
self.run_until(any_stuck, max_steps)
|
|
506
|
+
return self.diagnose_stuck_agents(threshold)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def main():
|
|
510
|
+
"""Example usage of debug harness."""
|
|
511
|
+
print("Creating debug harness from cogsguard recipe...")
|
|
512
|
+
harness = DebugHarness.from_recipe(num_agents=10, max_steps=1000, seed=42)
|
|
513
|
+
|
|
514
|
+
print("\nSimulation info at start:")
|
|
515
|
+
harness.print_simulation_info()
|
|
516
|
+
|
|
517
|
+
print("\nRunning 50 steps...")
|
|
518
|
+
harness.step(50)
|
|
519
|
+
harness.print_agent_summary()
|
|
520
|
+
|
|
521
|
+
print("\nRunning until agents get stuck (or 200 steps)...")
|
|
522
|
+
stuck = harness.run_until_stuck(threshold=10, max_steps=200)
|
|
523
|
+
|
|
524
|
+
if stuck:
|
|
525
|
+
print(f"\nFound {len(stuck)} stuck agents. Running coordinate diagnosis...")
|
|
526
|
+
harness.diagnose_coordinate_system()
|
|
527
|
+
else:
|
|
528
|
+
print("\nNo stuck agents found.")
|
|
529
|
+
harness.print_agent_summary()
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
if __name__ == "__main__":
|
|
533
|
+
main()
|