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,333 @@
1
+ """
2
+ Aligner role for CoGsGuard.
3
+
4
+ Aligners find supply depots and align them to the cogs commons to take control.
5
+ With aligner gear, they get +20 influence capacity.
6
+
7
+ Strategy:
8
+ - Find ALL junctions on the map
9
+ - Prioritize aligning neutral and enemy (clips) junctions
10
+ - Systematically work through all junctions to take them over
11
+ - Check energy before moving to targets
12
+ - Retry failed align actions up to MAX_RETRIES times
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Optional
18
+
19
+ from cogames_agents.policy.scripted_agent.utils import is_adjacent
20
+ from mettagrid.simulator import Action
21
+
22
+ from .policy import DEBUG, CogsguardAgentPolicyImpl
23
+ from .types import CogsguardAgentState, Role, StructureType
24
+
25
+ # Maximum number of times to retry a failed align action
26
+ MAX_RETRIES = 3
27
+ # HP buffer to start returning to the hub before gear is lost.
28
+ HP_RETURN_BUFFER = 12
29
+
30
+
31
+ class AlignerAgentPolicyImpl(CogsguardAgentPolicyImpl):
32
+ """Aligner agent: align ALL supply depots to cogs."""
33
+
34
+ ROLE = Role.ALIGNER
35
+
36
+ def execute_role(self, s: CogsguardAgentState) -> Action:
37
+ """Execute aligner behavior: find and align ALL supply depots.
38
+
39
+ Energy-aware behavior:
40
+ - Check if we have enough energy before attempting to move to targets
41
+ - If energy is low, go recharge at the nexus
42
+ - Retry failed align actions up to MAX_RETRIES times
43
+ - Require gear, heart, and influence before attempting to align
44
+ - If gear acquisition fails repeatedly, get hearts first
45
+ """
46
+ if DEBUG and s.step_count % 50 == 0:
47
+ num_junctions = len(s.get_structures_by_type(StructureType.CHARGER))
48
+ num_worked = len(s.worked_junctions)
49
+ print(
50
+ f"[A{s.agent_id}] ALIGNER: step={s.step_count} influence={s.influence} "
51
+ f"heart={s.heart} energy={s.energy} gear={s.aligner} "
52
+ f"junctions_known={num_junctions} worked={num_worked}"
53
+ )
54
+
55
+ hub_pos = s.get_structure_position(StructureType.HUB)
56
+ if hub_pos is not None:
57
+ dist_to_hub = abs(hub_pos[0] - s.row) + abs(hub_pos[1] - s.col)
58
+ if s.hp <= dist_to_hub + HP_RETURN_BUFFER:
59
+ if DEBUG and s.step_count % 10 == 0:
60
+ print(f"[A{s.agent_id}] ALIGNER: Low HP ({s.hp}), returning to hub")
61
+ return self._do_recharge(s)
62
+
63
+ # === Resource check: need gear, heart, and influence to align ===
64
+ has_gear = s.aligner >= 1
65
+ has_heart = s.heart >= 1
66
+ has_influence = s.influence >= 1
67
+
68
+ # If we don't have gear, try to get it
69
+ if not has_gear:
70
+ return self._handle_no_gear(s)
71
+
72
+ # If we have gear but are missing resources, go get them
73
+ if not has_heart or not has_influence:
74
+ if DEBUG and s.step_count % 10 == 0:
75
+ print(
76
+ f"[A{s.agent_id}] ALIGNER: Have gear but missing resources "
77
+ f"(heart={has_heart}, influence={has_influence}), getting them first"
78
+ )
79
+ return self._get_resources(s, need_influence=not has_influence, need_heart=not has_heart)
80
+
81
+ # Check if last action succeeded (for retry logic)
82
+ # Actions can fail due to insufficient energy - agents auto-regen so just retry
83
+ if s._pending_action_type == "align":
84
+ target = s._pending_action_target
85
+ if s.check_action_success():
86
+ if DEBUG:
87
+ print(f"[A{s.agent_id}] ALIGNER: Previous align succeeded!")
88
+ if target is not None and self._smart_role_coordinator is not None:
89
+ hub_pos = s.stations.get("hub")
90
+ self._smart_role_coordinator.register_junction_alignment(
91
+ target,
92
+ "cogs",
93
+ hub_pos,
94
+ s.step_count,
95
+ )
96
+ elif s.should_retry_action(MAX_RETRIES):
97
+ retry_count = s.increment_retry()
98
+ if DEBUG:
99
+ print(
100
+ f"[A{s.agent_id}] ALIGNER: Align failed, retrying ({retry_count}/{MAX_RETRIES}) "
101
+ f"at {s._pending_action_target}"
102
+ )
103
+ # Retry the same action - agent will have auto-regenerated some energy
104
+ if s._pending_action_target and is_adjacent((s.row, s.col), s._pending_action_target):
105
+ return self._use_object_at(s, s._pending_action_target)
106
+ else:
107
+ if DEBUG:
108
+ print(f"[A{s.agent_id}] ALIGNER: Align failed after {MAX_RETRIES} retries, moving on")
109
+ if target is not None and target == s._pending_alignment_target:
110
+ s._pending_alignment_target = None
111
+ s.clear_pending_action()
112
+
113
+ # Find the best depot to align (prioritize closest non-cogs junction)
114
+ target_depot = None
115
+ pending_target = s._pending_alignment_target
116
+ if pending_target is not None:
117
+ pending_struct = s.get_structure_at(pending_target)
118
+ if pending_struct is None or pending_struct.structure_type == StructureType.CHARGER:
119
+ if pending_struct is None or pending_struct.alignment in (None, "neutral"):
120
+ target_depot = pending_target
121
+ elif pending_struct.alignment == "cogs":
122
+ s._pending_alignment_target = None
123
+
124
+ if target_depot is None:
125
+ target_depot = self._find_best_target(s)
126
+
127
+ if target_depot is None:
128
+ if DEBUG and s.step_count % 50 == 0:
129
+ print(f"[A{s.agent_id}] ALIGNER: No targets, exploring for junctions")
130
+ return self._explore_for_junctions(s)
131
+
132
+ # Navigate to depot
133
+ # Note: moves require energy. If move fails due to low energy,
134
+ # action failure detection will catch it and we'll retry next step
135
+ # (agents auto-regen energy every step, and regen full near aligned buildings)
136
+ if not is_adjacent((s.row, s.col), target_depot):
137
+ if DEBUG and s.step_count % 20 == 0:
138
+ print(f"[A{s.agent_id}] ALIGNER: Moving to junction at {target_depot}")
139
+ return self._move_towards(s, target_depot, reach_adjacent=True)
140
+
141
+ # Align the depot by bumping it
142
+ # Mark this junction as worked for a while (align multiple times then move on)
143
+ last_worked = s.worked_junctions.get(target_depot, 0)
144
+ times_worked = s.step_count - last_worked if last_worked > 0 else 0
145
+ s.worked_junctions[target_depot] = s.step_count
146
+
147
+ # Start tracking this align attempt
148
+ s.start_action_attempt("align", target_depot)
149
+
150
+ if DEBUG and times_worked < 5:
151
+ print(f"[A{s.agent_id}] ALIGNER: ALIGNING junction at {target_depot} (energy={s.energy}, heart={s.heart})!")
152
+ return self._use_object_at(s, target_depot)
153
+
154
+ def _handle_no_gear(self, s: CogsguardAgentState) -> Action:
155
+ """Handle behavior when aligner doesn't have gear.
156
+
157
+ Strategy: Go to gear station and wait there until gear is available.
158
+ Can't do much without gear, so just wait.
159
+ """
160
+ station_pos = s.get_structure_position(StructureType.ALIGNER_STATION)
161
+
162
+ # If we don't know where the station is, explore to find it
163
+ if station_pos is None:
164
+ if DEBUG:
165
+ print(f"[A{s.agent_id}] ALIGNER_NO_GEAR: Station unknown, exploring")
166
+ return self._explore(s)
167
+
168
+ # Go to gear station
169
+ if not is_adjacent((s.row, s.col), station_pos):
170
+ if DEBUG and s.step_count % 10 == 0:
171
+ print(f"[A{s.agent_id}] ALIGNER_NO_GEAR: Moving to station at {station_pos}")
172
+ return self._move_towards(s, station_pos, reach_adjacent=True)
173
+
174
+ # At station - keep trying to get gear
175
+ if DEBUG and s.step_count % 10 == 0:
176
+ print(f"[A{s.agent_id}] ALIGNER_NO_GEAR: At station, waiting for gear")
177
+ return self._use_object_at(s, station_pos)
178
+
179
+ def _get_resources(self, s: CogsguardAgentState, need_influence: bool, need_heart: bool) -> Action:
180
+ """Get hearts from the chest (primary source).
181
+
182
+ The chest can produce hearts from resources:
183
+ 1. First tries to withdraw existing hearts from cogs commons (get_heart handler)
184
+ 2. If no hearts available, converts 1 of each element into 1 heart (make_heart handler)
185
+
186
+ So as long as miners deposit resources, aligners can get hearts.
187
+ If we've been trying to get hearts for too long, go explore instead.
188
+ """
189
+ if need_heart:
190
+ # If we've waited more than 40 steps for hearts, go explore instead
191
+ if s._heart_wait_start == 0:
192
+ s._heart_wait_start = s.step_count
193
+ if s.step_count - s._heart_wait_start > 40:
194
+ if DEBUG:
195
+ print(f"[A{s.agent_id}] ALIGNER: Waited 40+ steps for hearts, exploring instead")
196
+ s._heart_wait_start = 0
197
+ return self._explore_for_junctions(s)
198
+
199
+ # Try chest first - it's the primary heart source
200
+ chest_pos = s.get_structure_position(StructureType.CHEST)
201
+ if chest_pos is not None:
202
+ if DEBUG and s.step_count % 10 == 0:
203
+ adj = is_adjacent((s.row, s.col), chest_pos)
204
+ print(f"[A{s.agent_id}] ALIGNER: Getting hearts from chest at {chest_pos}, adjacent={adj}")
205
+ if not is_adjacent((s.row, s.col), chest_pos):
206
+ return self._move_towards(s, chest_pos, reach_adjacent=True)
207
+ return self._use_object_at(s, chest_pos)
208
+
209
+ # Try hub as fallback (may have heart AOE or deposit function)
210
+ hub_pos = s.get_structure_position(StructureType.HUB)
211
+ if hub_pos is not None:
212
+ if DEBUG:
213
+ print(f"[A{s.agent_id}] ALIGNER: No chest found, trying hub at {hub_pos}")
214
+ if not is_adjacent((s.row, s.col), hub_pos):
215
+ return self._move_towards(s, hub_pos, reach_adjacent=True)
216
+ return self._use_object_at(s, hub_pos)
217
+
218
+ # Neither found - explore to find them
219
+ if DEBUG:
220
+ print(f"[A{s.agent_id}] ALIGNER: No chest/hub found, exploring")
221
+ s._heart_wait_start = 0
222
+ return self._explore(s)
223
+
224
+ # Just need influence - wait for AOE regeneration near hub
225
+ hub_pos = s.get_structure_position(StructureType.HUB)
226
+ if hub_pos is None:
227
+ return self._explore(s)
228
+ if not is_adjacent((s.row, s.col), hub_pos):
229
+ return self._move_towards(s, hub_pos, reach_adjacent=True)
230
+ return self._noop()
231
+
232
+ def _find_best_target(self, s: CogsguardAgentState) -> Optional[tuple[int, int]]:
233
+ """Find the closest un-aligned junction to align.
234
+
235
+ Prioritizes by distance - aligns the closest junction that isn't already cogs-aligned.
236
+ Skips junctions that were recently worked on to ensure we visit multiple junctions.
237
+ """
238
+ # Get all known junctions from structures map
239
+ junctions = s.get_structures_by_type(StructureType.CHARGER)
240
+
241
+ # How long to ignore a junction after working on it (steps)
242
+ cooldown = 50
243
+
244
+ recent_candidates: list[tuple[int, tuple[int, int]]] = []
245
+ if self._smart_role_coordinator is not None:
246
+ hub_pos = s.stations.get("hub")
247
+ recent_targets = self._smart_role_coordinator.recent_scramble_targets(hub_pos, s.step_count)
248
+ for pos in recent_targets:
249
+ last_worked = s.worked_junctions.get(pos, 0)
250
+ if last_worked > 0 and s.step_count - last_worked < cooldown:
251
+ continue
252
+ junction = s.get_structure_at(pos)
253
+ if junction is not None and junction.alignment in ("cogs", "clips"):
254
+ continue
255
+ dist = abs(pos[0] - s.row) + abs(pos[1] - s.col)
256
+ recent_candidates.append((dist, pos))
257
+
258
+ if recent_candidates:
259
+ recent_candidates.sort()
260
+ target_idx = 0
261
+ if self._smart_role_coordinator is not None:
262
+ aligner_ids = sorted(
263
+ agent_id
264
+ for agent_id, snapshot in self._smart_role_coordinator.agent_snapshots.items()
265
+ if snapshot.role == Role.ALIGNER
266
+ )
267
+ if aligner_ids:
268
+ target_idx = aligner_ids.index(s.agent_id) if s.agent_id in aligner_ids else 0
269
+ return recent_candidates[target_idx % len(recent_candidates)][1]
270
+
271
+ # Collect all un-aligned junctions (not cogs) and sort by distance
272
+ unaligned_junctions: list[tuple[int, tuple[int, int]]] = []
273
+
274
+ for junction in junctions:
275
+ pos = junction.position
276
+ dist = abs(pos[0] - s.row) + abs(pos[1] - s.col)
277
+
278
+ # Skip recently worked junctions
279
+ last_worked = s.worked_junctions.get(pos, 0)
280
+ if last_worked > 0 and s.step_count - last_worked < cooldown:
281
+ continue
282
+
283
+ # Skip already cogs-aligned junctions
284
+ if junction.alignment is not None:
285
+ continue
286
+
287
+ # Add neutral junctions only
288
+ unaligned_junctions.append((dist, pos))
289
+
290
+ # Sort by distance and return closest
291
+ if unaligned_junctions:
292
+ unaligned_junctions.sort()
293
+ if DEBUG and s.step_count % 20 == 0:
294
+ count = len(unaligned_junctions)
295
+ closest = unaligned_junctions[0][1]
296
+ print(f"[A{s.agent_id}] ALIGNER: Found {count} un-aligned junctions, closest at {closest}")
297
+ target_idx = 0
298
+ if self._smart_role_coordinator is not None:
299
+ aligner_ids = sorted(
300
+ agent_id
301
+ for agent_id, snapshot in self._smart_role_coordinator.agent_snapshots.items()
302
+ if snapshot.role == Role.ALIGNER
303
+ )
304
+ if aligner_ids:
305
+ target_idx = aligner_ids.index(s.agent_id) if s.agent_id in aligner_ids else 0
306
+ return unaligned_junctions[target_idx % len(unaligned_junctions)][1]
307
+
308
+ return None
309
+
310
+ def _explore_for_junctions(self, s: CogsguardAgentState) -> Action:
311
+ """Explore aggressively to find more junctions spread around the map."""
312
+ frontier_action = self._explore_frontier(s)
313
+ if frontier_action is not None:
314
+ return frontier_action
315
+
316
+ # Move in a direction based on agent ID and step count to spread out
317
+ directions = ["north", "south", "east", "west"]
318
+ # Cycle through directions, spending 20 steps in each direction
319
+ dir_idx = (s.agent_id + s.step_count // 20) % 4
320
+ direction = directions[dir_idx]
321
+
322
+ dr, dc = self._move_deltas[direction]
323
+ next_r, next_c = s.row + dr, s.col + dc
324
+
325
+ # Check if we can move in that direction
326
+ from cogames_agents.policy.scripted_agent.pathfinding import is_traversable
327
+ from cogames_agents.policy.scripted_agent.types import CellType
328
+
329
+ if is_traversable(s, next_r, next_c, CellType): # type: ignore[arg-type]
330
+ return self._move(direction)
331
+
332
+ # Fall back to regular exploration if blocked
333
+ return self._explore(s)
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Callable
4
+
5
+ from mettagrid.simulator import Action
6
+
7
+ from .policy import CogsguardMultiRoleImpl
8
+ from .types import CogsguardAgentState, Role
9
+
10
+ BehaviorHook = Callable[[CogsguardAgentState], Action]
11
+
12
+
13
+ def build_cogsguard_behavior_hooks(policy: CogsguardMultiRoleImpl) -> dict[str, BehaviorHook]:
14
+ """Bind evolutionary behavior names to existing role implementations.
15
+
16
+ These hooks are not wired into the live policy yet, but they allow the
17
+ evolutionary coordinator to execute real behavior logic when integrated.
18
+ """
19
+ miner = policy._get_role_impl(Role.MINER)
20
+ scout = policy._get_role_impl(Role.SCOUT)
21
+ aligner = policy._get_role_impl(Role.ALIGNER)
22
+ scrambler = policy._get_role_impl(Role.SCRAMBLER)
23
+
24
+ def _discover_with_scout(s: CogsguardAgentState) -> Action:
25
+ return scout.execute_role(s)
26
+
27
+ def _get_influence(s: CogsguardAgentState) -> Action:
28
+ return aligner._get_resources(s, need_influence=True, need_heart=False)
29
+
30
+ return {
31
+ "explore": policy._explore,
32
+ "recharge": policy._do_recharge,
33
+ "mine_resource": miner._do_gather,
34
+ "deposit_resource": miner._do_deposit,
35
+ "find_extractor": miner._do_gather,
36
+ "discover_stations": _discover_with_scout,
37
+ "discover_extractors": _discover_with_scout,
38
+ "discover_junctions": _discover_with_scout,
39
+ "get_hearts": scrambler._get_hearts,
40
+ "get_influence": _get_influence,
41
+ "align_junction": aligner.execute_role,
42
+ "scramble_junction": scrambler.execute_role,
43
+ "find_enemy_junction": scrambler.execute_role,
44
+ }