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.
Files changed (128) hide show
  1. cogames_agents/__init__.py +0 -0
  2. cogames_agents/evals/__init__.py +5 -0
  3. cogames_agents/evals/planky_evals.py +415 -0
  4. cogames_agents/policy/__init__.py +0 -0
  5. cogames_agents/policy/evolution/__init__.py +0 -0
  6. cogames_agents/policy/evolution/cogsguard/__init__.py +0 -0
  7. cogames_agents/policy/evolution/cogsguard/evolution.py +695 -0
  8. cogames_agents/policy/evolution/cogsguard/evolutionary_coordinator.py +540 -0
  9. cogames_agents/policy/nim_agents/__init__.py +20 -0
  10. cogames_agents/policy/nim_agents/agents.py +98 -0
  11. cogames_agents/policy/nim_agents/bindings/generated/libnim_agents.dylib +0 -0
  12. cogames_agents/policy/nim_agents/bindings/generated/nim_agents.py +215 -0
  13. cogames_agents/policy/nim_agents/cogsguard_agents.nim +555 -0
  14. cogames_agents/policy/nim_agents/cogsguard_align_all_agents.nim +569 -0
  15. cogames_agents/policy/nim_agents/common.nim +1054 -0
  16. cogames_agents/policy/nim_agents/install.sh +1 -0
  17. cogames_agents/policy/nim_agents/ladybug_agent.nim +954 -0
  18. cogames_agents/policy/nim_agents/nim_agents.nim +68 -0
  19. cogames_agents/policy/nim_agents/nim_agents.nims +14 -0
  20. cogames_agents/policy/nim_agents/nimby.lock +3 -0
  21. cogames_agents/policy/nim_agents/racecar_agents.nim +844 -0
  22. cogames_agents/policy/nim_agents/random_agents.nim +68 -0
  23. cogames_agents/policy/nim_agents/test_agents.py +53 -0
  24. cogames_agents/policy/nim_agents/thinky_agents.nim +677 -0
  25. cogames_agents/policy/nim_agents/thinky_eval.py +230 -0
  26. cogames_agents/policy/scripted_agent/README.md +360 -0
  27. cogames_agents/policy/scripted_agent/__init__.py +0 -0
  28. cogames_agents/policy/scripted_agent/baseline_agent.py +1031 -0
  29. cogames_agents/policy/scripted_agent/cogas/__init__.py +5 -0
  30. cogames_agents/policy/scripted_agent/cogas/context.py +68 -0
  31. cogames_agents/policy/scripted_agent/cogas/entity_map.py +152 -0
  32. cogames_agents/policy/scripted_agent/cogas/goal.py +115 -0
  33. cogames_agents/policy/scripted_agent/cogas/goals/__init__.py +27 -0
  34. cogames_agents/policy/scripted_agent/cogas/goals/aligner.py +160 -0
  35. cogames_agents/policy/scripted_agent/cogas/goals/gear.py +197 -0
  36. cogames_agents/policy/scripted_agent/cogas/goals/miner.py +441 -0
  37. cogames_agents/policy/scripted_agent/cogas/goals/scout.py +40 -0
  38. cogames_agents/policy/scripted_agent/cogas/goals/scrambler.py +174 -0
  39. cogames_agents/policy/scripted_agent/cogas/goals/shared.py +160 -0
  40. cogames_agents/policy/scripted_agent/cogas/goals/stem.py +60 -0
  41. cogames_agents/policy/scripted_agent/cogas/goals/survive.py +100 -0
  42. cogames_agents/policy/scripted_agent/cogas/navigator.py +401 -0
  43. cogames_agents/policy/scripted_agent/cogas/obs_parser.py +238 -0
  44. cogames_agents/policy/scripted_agent/cogas/policy.py +525 -0
  45. cogames_agents/policy/scripted_agent/cogas/trace.py +69 -0
  46. cogames_agents/policy/scripted_agent/cogsguard/CLAUDE.md +517 -0
  47. cogames_agents/policy/scripted_agent/cogsguard/README.md +252 -0
  48. cogames_agents/policy/scripted_agent/cogsguard/__init__.py +74 -0
  49. cogames_agents/policy/scripted_agent/cogsguard/aligned_junction_held_investigation.md +152 -0
  50. cogames_agents/policy/scripted_agent/cogsguard/aligner.py +333 -0
  51. cogames_agents/policy/scripted_agent/cogsguard/behavior_hooks.py +44 -0
  52. cogames_agents/policy/scripted_agent/cogsguard/control_agent.py +323 -0
  53. cogames_agents/policy/scripted_agent/cogsguard/debug_agent.py +533 -0
  54. cogames_agents/policy/scripted_agent/cogsguard/miner.py +589 -0
  55. cogames_agents/policy/scripted_agent/cogsguard/options.py +67 -0
  56. cogames_agents/policy/scripted_agent/cogsguard/parity_metrics.py +36 -0
  57. cogames_agents/policy/scripted_agent/cogsguard/policy.py +1967 -0
  58. cogames_agents/policy/scripted_agent/cogsguard/prereq_trace.py +33 -0
  59. cogames_agents/policy/scripted_agent/cogsguard/role_trace.py +50 -0
  60. cogames_agents/policy/scripted_agent/cogsguard/roles.py +31 -0
  61. cogames_agents/policy/scripted_agent/cogsguard/rollout_trace.py +40 -0
  62. cogames_agents/policy/scripted_agent/cogsguard/scout.py +69 -0
  63. cogames_agents/policy/scripted_agent/cogsguard/scrambler.py +350 -0
  64. cogames_agents/policy/scripted_agent/cogsguard/targeted_agent.py +418 -0
  65. cogames_agents/policy/scripted_agent/cogsguard/teacher.py +224 -0
  66. cogames_agents/policy/scripted_agent/cogsguard/types.py +381 -0
  67. cogames_agents/policy/scripted_agent/cogsguard/v2_agent.py +49 -0
  68. cogames_agents/policy/scripted_agent/common/__init__.py +0 -0
  69. cogames_agents/policy/scripted_agent/common/geometry.py +24 -0
  70. cogames_agents/policy/scripted_agent/common/roles.py +34 -0
  71. cogames_agents/policy/scripted_agent/common/tag_utils.py +48 -0
  72. cogames_agents/policy/scripted_agent/demo_policy.py +242 -0
  73. cogames_agents/policy/scripted_agent/pathfinding.py +126 -0
  74. cogames_agents/policy/scripted_agent/pinky/DESIGN.md +317 -0
  75. cogames_agents/policy/scripted_agent/pinky/__init__.py +5 -0
  76. cogames_agents/policy/scripted_agent/pinky/behaviors/__init__.py +17 -0
  77. cogames_agents/policy/scripted_agent/pinky/behaviors/aligner.py +400 -0
  78. cogames_agents/policy/scripted_agent/pinky/behaviors/base.py +119 -0
  79. cogames_agents/policy/scripted_agent/pinky/behaviors/miner.py +632 -0
  80. cogames_agents/policy/scripted_agent/pinky/behaviors/scout.py +138 -0
  81. cogames_agents/policy/scripted_agent/pinky/behaviors/scrambler.py +433 -0
  82. cogames_agents/policy/scripted_agent/pinky/policy.py +570 -0
  83. cogames_agents/policy/scripted_agent/pinky/services/__init__.py +7 -0
  84. cogames_agents/policy/scripted_agent/pinky/services/map_tracker.py +808 -0
  85. cogames_agents/policy/scripted_agent/pinky/services/navigator.py +864 -0
  86. cogames_agents/policy/scripted_agent/pinky/services/safety.py +189 -0
  87. cogames_agents/policy/scripted_agent/pinky/state.py +299 -0
  88. cogames_agents/policy/scripted_agent/pinky/types.py +138 -0
  89. cogames_agents/policy/scripted_agent/planky/CLAUDE.md +124 -0
  90. cogames_agents/policy/scripted_agent/planky/IMPROVEMENTS.md +160 -0
  91. cogames_agents/policy/scripted_agent/planky/NOTES.md +153 -0
  92. cogames_agents/policy/scripted_agent/planky/PLAN.md +254 -0
  93. cogames_agents/policy/scripted_agent/planky/README.md +214 -0
  94. cogames_agents/policy/scripted_agent/planky/STRATEGY.md +100 -0
  95. cogames_agents/policy/scripted_agent/planky/__init__.py +5 -0
  96. cogames_agents/policy/scripted_agent/planky/context.py +68 -0
  97. cogames_agents/policy/scripted_agent/planky/entity_map.py +152 -0
  98. cogames_agents/policy/scripted_agent/planky/goal.py +107 -0
  99. cogames_agents/policy/scripted_agent/planky/goals/__init__.py +27 -0
  100. cogames_agents/policy/scripted_agent/planky/goals/aligner.py +168 -0
  101. cogames_agents/policy/scripted_agent/planky/goals/gear.py +179 -0
  102. cogames_agents/policy/scripted_agent/planky/goals/miner.py +416 -0
  103. cogames_agents/policy/scripted_agent/planky/goals/scout.py +40 -0
  104. cogames_agents/policy/scripted_agent/planky/goals/scrambler.py +174 -0
  105. cogames_agents/policy/scripted_agent/planky/goals/shared.py +160 -0
  106. cogames_agents/policy/scripted_agent/planky/goals/stem.py +49 -0
  107. cogames_agents/policy/scripted_agent/planky/goals/survive.py +96 -0
  108. cogames_agents/policy/scripted_agent/planky/navigator.py +388 -0
  109. cogames_agents/policy/scripted_agent/planky/obs_parser.py +238 -0
  110. cogames_agents/policy/scripted_agent/planky/policy.py +485 -0
  111. cogames_agents/policy/scripted_agent/planky/tests/__init__.py +0 -0
  112. cogames_agents/policy/scripted_agent/planky/tests/conftest.py +66 -0
  113. cogames_agents/policy/scripted_agent/planky/tests/helpers.py +152 -0
  114. cogames_agents/policy/scripted_agent/planky/tests/test_aligner.py +24 -0
  115. cogames_agents/policy/scripted_agent/planky/tests/test_miner.py +30 -0
  116. cogames_agents/policy/scripted_agent/planky/tests/test_scout.py +15 -0
  117. cogames_agents/policy/scripted_agent/planky/tests/test_scrambler.py +29 -0
  118. cogames_agents/policy/scripted_agent/planky/tests/test_stem.py +36 -0
  119. cogames_agents/policy/scripted_agent/planky/trace.py +69 -0
  120. cogames_agents/policy/scripted_agent/types.py +239 -0
  121. cogames_agents/policy/scripted_agent/unclipping_agent.py +461 -0
  122. cogames_agents/policy/scripted_agent/utils.py +381 -0
  123. cogames_agents/policy/scripted_registry.py +80 -0
  124. cogames_agents/py.typed +0 -0
  125. cogames_agents-0.0.0.7.dist-info/METADATA +98 -0
  126. cogames_agents-0.0.0.7.dist-info/RECORD +128 -0
  127. cogames_agents-0.0.0.7.dist-info/WHEEL +6 -0
  128. 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()