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,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for scripted agents.
|
|
3
|
+
|
|
4
|
+
Pure/stateless helper functions that can be reused across different agents.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any, Iterable, cast
|
|
10
|
+
|
|
11
|
+
from mettagrid.simulator import Action
|
|
12
|
+
from mettagrid.simulator.interface import AgentObservation
|
|
13
|
+
|
|
14
|
+
from .common.geometry import is_adjacent as geometry_is_adjacent
|
|
15
|
+
from .common.tag_utils import select_primary_tag
|
|
16
|
+
from .types import ObjectState, ParsedObservation, SimpleAgentState
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def is_adjacent(pos1: tuple[int, int], pos2: tuple[int, int]) -> bool:
|
|
20
|
+
"""Check if two positions are adjacent (4-way cardinal directions)."""
|
|
21
|
+
return geometry_is_adjacent(pos1, pos2)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_wall(obj_name: str) -> bool:
|
|
25
|
+
"""Check if an object name represents a wall or obstacle."""
|
|
26
|
+
return "wall" in obj_name or "#" in obj_name or obj_name in {"wall", "obstacle"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_station(obj_name: str, station: str) -> bool:
|
|
30
|
+
"""Check if an object name contains a specific station type."""
|
|
31
|
+
return station in obj_name
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def add_inventory_token(
|
|
35
|
+
inventory: dict[str, int],
|
|
36
|
+
feature_name: str,
|
|
37
|
+
value: int,
|
|
38
|
+
*,
|
|
39
|
+
token_value_base: int,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Add inventory token value, reconstructing multi-token amounts."""
|
|
42
|
+
suffix = feature_name[4:]
|
|
43
|
+
if ":p" in suffix:
|
|
44
|
+
resource_name, power_str = suffix.rsplit(":p", 1)
|
|
45
|
+
power = int(power_str)
|
|
46
|
+
else:
|
|
47
|
+
resource_name = suffix
|
|
48
|
+
power = 0
|
|
49
|
+
inventory[resource_name] = inventory.get(resource_name, 0) + value * (token_value_base**power)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def process_feature_at_position(
|
|
53
|
+
position_features: dict[tuple[int, int], dict[str, Any]],
|
|
54
|
+
pos: tuple[int, int],
|
|
55
|
+
feature_name: str,
|
|
56
|
+
value: int,
|
|
57
|
+
*,
|
|
58
|
+
spatial_feature_names: set[str],
|
|
59
|
+
agent_feature_key_by_name: dict[str, str],
|
|
60
|
+
protocol_input_prefix: str,
|
|
61
|
+
protocol_output_prefix: str,
|
|
62
|
+
) -> None:
|
|
63
|
+
"""Process a single observation feature and add it to position_features."""
|
|
64
|
+
if pos not in position_features:
|
|
65
|
+
position_features[pos] = {}
|
|
66
|
+
position_entry = position_features[pos]
|
|
67
|
+
|
|
68
|
+
# Handle spatial features (tag, cooldown, etc.)
|
|
69
|
+
if feature_name in spatial_feature_names:
|
|
70
|
+
# Tag: collect all tags as a list (objects can have multiple tags)
|
|
71
|
+
if feature_name == "tag":
|
|
72
|
+
tags_value = position_entry.get("tags")
|
|
73
|
+
if not isinstance(tags_value, list):
|
|
74
|
+
tags_value = []
|
|
75
|
+
position_entry["tags"] = tags_value
|
|
76
|
+
cast(list[int], tags_value).append(value)
|
|
77
|
+
return
|
|
78
|
+
# Other spatial features are single values
|
|
79
|
+
position_entry[feature_name] = value
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# Handle agent features (agent:group -> agent_group, etc.)
|
|
83
|
+
agent_feature_key = agent_feature_key_by_name.get(feature_name)
|
|
84
|
+
if agent_feature_key is not None:
|
|
85
|
+
position_entry[agent_feature_key] = value
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Handle protocol features (recipes)
|
|
89
|
+
if feature_name.startswith(protocol_input_prefix):
|
|
90
|
+
resource = feature_name[len(protocol_input_prefix) :]
|
|
91
|
+
inputs_value = position_entry.get("protocol_inputs")
|
|
92
|
+
if not isinstance(inputs_value, dict):
|
|
93
|
+
inputs_value = {}
|
|
94
|
+
position_entry["protocol_inputs"] = inputs_value
|
|
95
|
+
cast(dict[str, int], inputs_value)[resource] = value
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
if feature_name.startswith(protocol_output_prefix):
|
|
99
|
+
resource = feature_name[len(protocol_output_prefix) :]
|
|
100
|
+
outputs_value = position_entry.get("protocol_outputs")
|
|
101
|
+
if not isinstance(outputs_value, dict):
|
|
102
|
+
outputs_value = {}
|
|
103
|
+
position_entry["protocol_outputs"] = outputs_value
|
|
104
|
+
cast(dict[str, int], outputs_value)[resource] = value
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def has_type_tag(tags: Iterable[str], tokens: Iterable[str]) -> bool:
|
|
109
|
+
for tag in tags:
|
|
110
|
+
if not tag.startswith("type:"):
|
|
111
|
+
continue
|
|
112
|
+
type_name = tag.split(":", 1)[1]
|
|
113
|
+
if any(token in type_name for token in tokens):
|
|
114
|
+
return True
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def create_object_state(
|
|
119
|
+
features: dict[str, Any],
|
|
120
|
+
*,
|
|
121
|
+
tag_names: dict[int, str],
|
|
122
|
+
) -> ObjectState:
|
|
123
|
+
"""Create an ObjectState from collected features.
|
|
124
|
+
|
|
125
|
+
Note: Objects can have multiple tags (e.g., "wall" + "green" vibe).
|
|
126
|
+
Prefer type tags over collective tags for the primary object name.
|
|
127
|
+
"""
|
|
128
|
+
# Get tags list (now stored as "tags" instead of "tag")
|
|
129
|
+
tags_value = features.get("tags", [])
|
|
130
|
+
if isinstance(tags_value, list):
|
|
131
|
+
tag_ids = list(tags_value)
|
|
132
|
+
elif isinstance(tags_value, int):
|
|
133
|
+
tag_ids = [tags_value]
|
|
134
|
+
else:
|
|
135
|
+
tag_ids = []
|
|
136
|
+
|
|
137
|
+
# Pick a primary object name with tag precedence.
|
|
138
|
+
if tag_ids:
|
|
139
|
+
tags = [tag_names.get(tag_id, f"unknown_tag_{tag_id}") for tag_id in tag_ids]
|
|
140
|
+
obj_name = select_primary_tag(tags)
|
|
141
|
+
else:
|
|
142
|
+
tags = []
|
|
143
|
+
obj_name = "unknown"
|
|
144
|
+
|
|
145
|
+
# Helper to safely extract int values
|
|
146
|
+
def get_int(key: str, default: int) -> int:
|
|
147
|
+
val = features.get(key, default)
|
|
148
|
+
return int(val) if isinstance(val, int) else default
|
|
149
|
+
|
|
150
|
+
# Helper to safely extract dict values
|
|
151
|
+
def get_dict(key: str) -> dict[str, int]:
|
|
152
|
+
val = features.get(key, {})
|
|
153
|
+
return dict(val) if isinstance(val, dict) else {}
|
|
154
|
+
|
|
155
|
+
return ObjectState(
|
|
156
|
+
name=obj_name,
|
|
157
|
+
tags=tags,
|
|
158
|
+
cooldown_remaining=get_int("cooldown_remaining", 0),
|
|
159
|
+
clipped=get_int("clipped", 0),
|
|
160
|
+
remaining_uses=get_int("remaining_uses", 999),
|
|
161
|
+
inventory=get_dict("inventory"),
|
|
162
|
+
protocol_inputs=get_dict("protocol_inputs"),
|
|
163
|
+
protocol_outputs=get_dict("protocol_outputs"),
|
|
164
|
+
agent_group=get_int("agent_group", -1),
|
|
165
|
+
agent_frozen=get_int("agent_frozen", 0),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def read_inventory_from_obs(
|
|
170
|
+
state: SimpleAgentState,
|
|
171
|
+
obs: AgentObservation,
|
|
172
|
+
*,
|
|
173
|
+
obs_hr: int,
|
|
174
|
+
obs_wr: int,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Read inventory from observation tokens at center cell and update state."""
|
|
177
|
+
inv = {}
|
|
178
|
+
token_value_base = None
|
|
179
|
+
center_r, center_c = obs_hr, obs_wr
|
|
180
|
+
for tok in obs.tokens:
|
|
181
|
+
if tok.location == (center_r, center_c):
|
|
182
|
+
feature_name = tok.feature.name
|
|
183
|
+
if feature_name.startswith("inv:"):
|
|
184
|
+
if token_value_base is None:
|
|
185
|
+
token_value_base = int(tok.feature.normalization)
|
|
186
|
+
add_inventory_token(inv, feature_name, tok.value, token_value_base=token_value_base)
|
|
187
|
+
|
|
188
|
+
state.energy = inv.get("energy", 0)
|
|
189
|
+
state.carbon = inv.get("carbon", 0)
|
|
190
|
+
state.oxygen = inv.get("oxygen", 0)
|
|
191
|
+
state.germanium = inv.get("germanium", 0)
|
|
192
|
+
state.silicon = inv.get("silicon", 0)
|
|
193
|
+
state.hearts = inv.get("heart", 0)
|
|
194
|
+
state.decoder = inv.get("decoder", 0)
|
|
195
|
+
state.modulator = inv.get("modulator", 0)
|
|
196
|
+
state.resonator = inv.get("resonator", 0)
|
|
197
|
+
state.scrambler = inv.get("scrambler", 0)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def parse_observation(
|
|
201
|
+
state: SimpleAgentState,
|
|
202
|
+
obs: AgentObservation,
|
|
203
|
+
*,
|
|
204
|
+
obs_hr: int,
|
|
205
|
+
obs_wr: int,
|
|
206
|
+
spatial_feature_names: set[str],
|
|
207
|
+
agent_feature_key_by_name: dict[str, str],
|
|
208
|
+
protocol_input_prefix: str,
|
|
209
|
+
protocol_output_prefix: str,
|
|
210
|
+
tag_names: dict[int, str],
|
|
211
|
+
debug: bool = False,
|
|
212
|
+
) -> ParsedObservation:
|
|
213
|
+
"""Parse token-based observation into structured format.
|
|
214
|
+
|
|
215
|
+
AgentObservation with tokens (ObservationToken list)
|
|
216
|
+
- Agent inventory is obtained via agent.inventory (not parsed here)
|
|
217
|
+
- Spatial features are parsed from observations, including object inventories
|
|
218
|
+
|
|
219
|
+
Converts egocentric spatial coordinates to world coordinates using agent position.
|
|
220
|
+
Agent position (agent_row, agent_col) comes from simulation.grid_objects().
|
|
221
|
+
"""
|
|
222
|
+
# First pass: collect all spatial features by position
|
|
223
|
+
position_features: dict[tuple[int, int], dict[str, Any]] = {}
|
|
224
|
+
token_value_base = None
|
|
225
|
+
|
|
226
|
+
for tok in obs.tokens:
|
|
227
|
+
obs_r, obs_c = tok.location
|
|
228
|
+
feature_name = tok.feature.name
|
|
229
|
+
value = tok.value
|
|
230
|
+
|
|
231
|
+
# Skip center location - that's inventory/global obs, obtained via agent.inventory
|
|
232
|
+
if obs_r == obs_hr and obs_c == obs_wr:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
# Convert observation-relative coords to world coords
|
|
236
|
+
if state.row >= 0 and state.col >= 0:
|
|
237
|
+
r = obs_r - obs_hr + state.row
|
|
238
|
+
c = obs_c - obs_wr + state.col
|
|
239
|
+
|
|
240
|
+
if 0 <= r < state.map_height and 0 <= c < state.map_width:
|
|
241
|
+
if feature_name.startswith("inv:"):
|
|
242
|
+
if token_value_base is None:
|
|
243
|
+
token_value_base = int(tok.feature.normalization)
|
|
244
|
+
position_entry = position_features.setdefault((r, c), {})
|
|
245
|
+
inventory_value = position_entry.get("inventory")
|
|
246
|
+
if not isinstance(inventory_value, dict):
|
|
247
|
+
inventory_value = {}
|
|
248
|
+
position_entry["inventory"] = inventory_value
|
|
249
|
+
add_inventory_token(
|
|
250
|
+
cast(dict[str, int], inventory_value),
|
|
251
|
+
feature_name,
|
|
252
|
+
value,
|
|
253
|
+
token_value_base=token_value_base,
|
|
254
|
+
)
|
|
255
|
+
continue
|
|
256
|
+
process_feature_at_position(
|
|
257
|
+
position_features,
|
|
258
|
+
(r, c),
|
|
259
|
+
feature_name,
|
|
260
|
+
value,
|
|
261
|
+
spatial_feature_names=spatial_feature_names,
|
|
262
|
+
agent_feature_key_by_name=agent_feature_key_by_name,
|
|
263
|
+
protocol_input_prefix=protocol_input_prefix,
|
|
264
|
+
protocol_output_prefix=protocol_output_prefix,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Second pass: create ObjectState for each position with tags
|
|
268
|
+
nearby_objects = {
|
|
269
|
+
pos: create_object_state(features, tag_names=tag_names)
|
|
270
|
+
for pos, features in position_features.items()
|
|
271
|
+
if "tags" in features # Note: stored as "tags" (plural) to support multiple tags per object
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return ParsedObservation(
|
|
275
|
+
row=state.row,
|
|
276
|
+
col=state.col,
|
|
277
|
+
energy=0, # Inventory obtained via agent.inventory
|
|
278
|
+
carbon=0,
|
|
279
|
+
oxygen=0,
|
|
280
|
+
germanium=0,
|
|
281
|
+
silicon=0,
|
|
282
|
+
hearts=0,
|
|
283
|
+
decoder=0,
|
|
284
|
+
modulator=0,
|
|
285
|
+
resonator=0,
|
|
286
|
+
scrambler=0,
|
|
287
|
+
nearby_objects=nearby_objects,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def change_vibe_action(
|
|
292
|
+
vibe_name: str,
|
|
293
|
+
*,
|
|
294
|
+
action_names: list[str],
|
|
295
|
+
) -> Action:
|
|
296
|
+
"""
|
|
297
|
+
Return a safe vibe-change action.
|
|
298
|
+
Guard against disabled or single-vibe configurations before issuing the action.
|
|
299
|
+
"""
|
|
300
|
+
change_vibe_actions = [a for a in action_names if a.startswith("change_vibe_")]
|
|
301
|
+
if len(change_vibe_actions) <= 1:
|
|
302
|
+
return Action(name="noop")
|
|
303
|
+
action_name = f"change_vibe_{vibe_name}"
|
|
304
|
+
if action_name in action_names:
|
|
305
|
+
return Action(name=action_name)
|
|
306
|
+
available = [a[len("change_vibe_") :] for a in change_vibe_actions]
|
|
307
|
+
raise Exception(f"No valid vibe called '{vibe_name}'. Available vibes: {available}")
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def update_agent_position(
|
|
311
|
+
state: SimpleAgentState,
|
|
312
|
+
*,
|
|
313
|
+
move_deltas: dict[str, tuple[int, int]],
|
|
314
|
+
) -> None:
|
|
315
|
+
"""Update agent position based on last action.
|
|
316
|
+
|
|
317
|
+
Position is tracked relative to origin (starting position), using only movement deltas.
|
|
318
|
+
No dependency on simulation.grid_objects().
|
|
319
|
+
|
|
320
|
+
IMPORTANT: When using objects (extractors, stations), the agent "moves into" them but doesn't
|
|
321
|
+
actually change position. We detect this by checking the using_object_this_step flag.
|
|
322
|
+
"""
|
|
323
|
+
# If last action was a move and we're not using an object, update position
|
|
324
|
+
# We assume the move succeeded unless we were using an object
|
|
325
|
+
if state.last_action and state.last_action.name.startswith("move_") and not state.using_object_this_step:
|
|
326
|
+
# Extract direction from action name (e.g., "move_north" -> "north")
|
|
327
|
+
direction = state.last_action.name[5:] # Remove "move_" prefix
|
|
328
|
+
if direction in move_deltas:
|
|
329
|
+
dr, dc = move_deltas[direction]
|
|
330
|
+
state.row += dr
|
|
331
|
+
state.col += dc
|
|
332
|
+
# Clear the flag for next step
|
|
333
|
+
state.using_object_this_step = False
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def use_object_at(
|
|
337
|
+
state: SimpleAgentState,
|
|
338
|
+
target_pos: tuple[int, int],
|
|
339
|
+
) -> Action:
|
|
340
|
+
"""Use an object by moving into its cell. Sets a flag so position tracking knows not to update.
|
|
341
|
+
|
|
342
|
+
This is the generic "move into to use" action for extractors, hubs, chests, junctions, etc.
|
|
343
|
+
"""
|
|
344
|
+
action = move_into_cell(state, target_pos)
|
|
345
|
+
|
|
346
|
+
# Mark that we're using an object so position tracking doesn't update
|
|
347
|
+
state.using_object_this_step = True
|
|
348
|
+
|
|
349
|
+
return action
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def move_into_cell(
|
|
353
|
+
state: SimpleAgentState,
|
|
354
|
+
target: tuple[int, int],
|
|
355
|
+
) -> Action:
|
|
356
|
+
"""Return the action that attempts to step into the target cell.
|
|
357
|
+
|
|
358
|
+
Checks for agent occupancy before moving to avoid collisions.
|
|
359
|
+
"""
|
|
360
|
+
tr, tc = target
|
|
361
|
+
if state.row == tr and state.col == tc:
|
|
362
|
+
return Action(name="noop")
|
|
363
|
+
dr = tr - state.row
|
|
364
|
+
dc = tc - state.col
|
|
365
|
+
|
|
366
|
+
# Check if another agent is at the target position
|
|
367
|
+
if (tr, tc) in state.agent_occupancy:
|
|
368
|
+
# Another agent is blocking the target, wait or try alternative
|
|
369
|
+
# For a simple fallback, return noop (caller can handle random direction if needed)
|
|
370
|
+
return Action(name="noop")
|
|
371
|
+
|
|
372
|
+
if dr == -1:
|
|
373
|
+
return Action(name="move_north")
|
|
374
|
+
if dr == 1:
|
|
375
|
+
return Action(name="move_south")
|
|
376
|
+
if dc == 1:
|
|
377
|
+
return Action(name="move_east")
|
|
378
|
+
if dc == -1:
|
|
379
|
+
return Action(name="move_west")
|
|
380
|
+
# Fallback to noop if offsets unexpected
|
|
381
|
+
return Action(name="noop")
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Registry of scripted policy URIs derived from policy short_names."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ast
|
|
6
|
+
import functools
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Iterable, Optional
|
|
9
|
+
|
|
10
|
+
_POLICY_ROOT = Path(__file__).resolve().parent
|
|
11
|
+
_SCRIPTED_SCAN_DIRS = (
|
|
12
|
+
_POLICY_ROOT / "scripted_agent",
|
|
13
|
+
_POLICY_ROOT / "nim_agents",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _iter_policy_files() -> Iterable[Path]:
|
|
18
|
+
for base_dir in _SCRIPTED_SCAN_DIRS:
|
|
19
|
+
if not base_dir.exists():
|
|
20
|
+
continue
|
|
21
|
+
for path in base_dir.rglob("*.py"):
|
|
22
|
+
if path.name.startswith("__"):
|
|
23
|
+
continue
|
|
24
|
+
yield path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _extract_literal_strings(node: ast.AST) -> Optional[list[str]]:
|
|
28
|
+
if isinstance(node, (ast.List, ast.Tuple)):
|
|
29
|
+
values: list[str] = []
|
|
30
|
+
for elt in node.elts:
|
|
31
|
+
if isinstance(elt, ast.Constant) and isinstance(elt.value, str):
|
|
32
|
+
values.append(elt.value)
|
|
33
|
+
else:
|
|
34
|
+
return None
|
|
35
|
+
return values
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _extract_short_names_from_class(class_def: ast.ClassDef) -> list[str]:
|
|
40
|
+
for stmt in class_def.body:
|
|
41
|
+
if isinstance(stmt, ast.Assign):
|
|
42
|
+
for target in stmt.targets:
|
|
43
|
+
if isinstance(target, ast.Name) and target.id == "short_names":
|
|
44
|
+
value = _extract_literal_strings(stmt.value)
|
|
45
|
+
return value or []
|
|
46
|
+
if isinstance(stmt, ast.AnnAssign):
|
|
47
|
+
if isinstance(stmt.target, ast.Name) and stmt.target.id == "short_names":
|
|
48
|
+
if stmt.value is None:
|
|
49
|
+
return []
|
|
50
|
+
value = _extract_literal_strings(stmt.value)
|
|
51
|
+
return value or []
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@functools.cache
|
|
56
|
+
def list_scripted_agent_names() -> tuple[str, ...]:
|
|
57
|
+
names: set[str] = set()
|
|
58
|
+
for path in _iter_policy_files():
|
|
59
|
+
try:
|
|
60
|
+
source = path.read_text(encoding="utf-8")
|
|
61
|
+
except OSError:
|
|
62
|
+
continue
|
|
63
|
+
try:
|
|
64
|
+
tree = ast.parse(source, filename=str(path))
|
|
65
|
+
except SyntaxError:
|
|
66
|
+
continue
|
|
67
|
+
for node in tree.body:
|
|
68
|
+
if isinstance(node, ast.ClassDef):
|
|
69
|
+
names.update(_extract_short_names_from_class(node))
|
|
70
|
+
return tuple(sorted(names))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
SCRIPTED_AGENT_URIS: dict[str, str] = {name: f"metta://policy/{name}" for name in list_scripted_agent_names()}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def resolve_scripted_agent_uri(name: str) -> str:
|
|
77
|
+
if name in SCRIPTED_AGENT_URIS:
|
|
78
|
+
return SCRIPTED_AGENT_URIS[name]
|
|
79
|
+
available = ", ".join(sorted(SCRIPTED_AGENT_URIS))
|
|
80
|
+
raise ValueError(f"Unknown scripted agent '{name}'. Available: {available}")
|
cogames_agents/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cogames-agents
|
|
3
|
+
Version: 0.0.0.7
|
|
4
|
+
Summary: Optional agent policies for CoGames
|
|
5
|
+
Author: Metta AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Metta-AI/metta/tree/main/packages/cogames-agents
|
|
8
|
+
Project-URL: Repository, https://github.com/Metta-AI/metta
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Requires-Python: <3.13,>=3.12
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
Requires-Dist: cogames==0.3.64
|
|
16
|
+
Requires-Dist: mettagrid==0.2.0.74
|
|
17
|
+
Requires-Dist: numpy>=2.0.0
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest; extra == "test"
|
|
20
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
|
21
|
+
Requires-Dist: ruff; extra == "test"
|
|
22
|
+
|
|
23
|
+
# cogames-agents
|
|
24
|
+
|
|
25
|
+
Optional scripted policies for CoGames. Use them for quick baselines, play/eval smoke tests, or as teacher policies.
|
|
26
|
+
|
|
27
|
+
## Scripted policy registry
|
|
28
|
+
|
|
29
|
+
The registry at `cogames_agents.policy.scripted_registry` maps policy `short_names` to `metta://policy/...` URIs.
|
|
30
|
+
Scripted agents and teachers share these identifiers, so the same name works for evaluation, play, and
|
|
31
|
+
`TeacherConfig.policy_uri`.
|
|
32
|
+
|
|
33
|
+
To list the current names:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
python -c "from cogames_agents.policy.scripted_registry import list_scripted_agent_names; print(list_scripted_agent_names())"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Common scripted policy names include:
|
|
40
|
+
|
|
41
|
+
- Baselines: `baseline`, `tiny_baseline`, `ladybug_py`
|
|
42
|
+
- Nim baselines: `thinky`, `race_car`, `ladybug`, `nim_random`
|
|
43
|
+
- CogsGuard core: `role`, `role_py`, `wombo`
|
|
44
|
+
- CogsGuard variants: `alignall`, `cogsguard_control`, `cogsguard_targeted`, `cogsguard_v2`
|
|
45
|
+
- CogsGuard roles: `miner`, `scout`, `aligner`, `scrambler`
|
|
46
|
+
- Teacher: `teacher`
|
|
47
|
+
- Pinky: `pinky`
|
|
48
|
+
|
|
49
|
+
For the full registry snapshot, see `docs/scripted-agent-registry.md`.
|
|
50
|
+
|
|
51
|
+
Role-specific policies are exposed via role names (miner/scout/aligner/scrambler). For the teacher policy, you can pass
|
|
52
|
+
`role_vibes` as a comma-separated list:
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
metta://policy/teacher?role_vibes=miner,scout
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Fixed-role mixes and explicit orderings are configured via `role_py` parameters:
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
metta://policy/role_py?role_cycle=aligner,miner,scrambler,scout
|
|
64
|
+
metta://policy/role_py?role_order=aligner,miner,aligner,miner,scout
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pinky role counts are applied in a different order than CogsGuard:
|
|
68
|
+
|
|
69
|
+
- Pinky order: miner -> scout -> aligner -> scrambler, and any remaining agents stay default/noop.
|
|
70
|
+
- CogsGuard order: scrambler -> aligner -> miner -> scout, then fills remaining agents with gear.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
metta://policy/pinky?miner=4&aligner=2&scrambler=4
|
|
76
|
+
metta://policy/pinky?miner=2&scout=2&aligner=1&scrambler=1&debug=1
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Recipe usage
|
|
80
|
+
|
|
81
|
+
The `recipes.experiment.scripted_agents` recipe accepts the same scripted policy names:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
./tools/run.py recipes.experiment.scripted_agents.play agent=thinky suite=cvc_arena
|
|
85
|
+
./tools/run.py recipes.experiment.scripted_agents.play agent=miner suite=cogsguard
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Included policies
|
|
89
|
+
|
|
90
|
+
- Short names map to the fastest implementation (Nim when available, otherwise Python).
|
|
91
|
+
- `_nim` aliases exist when there is a Nim implementation alongside Python.
|
|
92
|
+
- See `docs/scripted-agent-registry.md` for the canonical short-name list.
|
|
93
|
+
- Teacher wrapper: `teacher` (`teacher_nim`) forces an initial role/vibe, then delegates to the Nim policy.
|
|
94
|
+
|
|
95
|
+
## Docs
|
|
96
|
+
|
|
97
|
+
- `docs/mettaboxes.md` (mettabox usage guide)
|
|
98
|
+
- `docs/aws-sso-on-mettabox.md` (AWS SSO login from inside mettabox containers)
|