cogames 0.3.49__py3-none-any.whl → 0.3.64__py3-none-any.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/cli/client.py +60 -6
- cogames/cli/docsync/__init__.py +0 -0
- cogames/cli/docsync/_nb_md_directive_processing.py +180 -0
- cogames/cli/docsync/_nb_md_sync.py +103 -0
- cogames/cli/docsync/_nb_py_sync.py +122 -0
- cogames/cli/docsync/_three_way_sync.py +115 -0
- cogames/cli/docsync/_utils.py +76 -0
- cogames/cli/docsync/docsync.py +156 -0
- cogames/cli/leaderboard.py +112 -28
- cogames/cli/mission.py +64 -53
- cogames/cli/policy.py +46 -10
- cogames/cli/submit.py +268 -67
- cogames/cogs_vs_clips/cog.py +79 -0
- cogames/cogs_vs_clips/cogs_vs_clips_mapgen.md +19 -16
- cogames/cogs_vs_clips/cogsguard_reward_variants.py +153 -0
- cogames/cogs_vs_clips/cogsguard_tutorial.py +56 -0
- cogames/cogs_vs_clips/evals/README.md +10 -16
- cogames/cogs_vs_clips/evals/cogsguard_evals.py +81 -0
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +49 -444
- cogames/cogs_vs_clips/evals/difficulty_variants.py +13 -326
- cogames/cogs_vs_clips/evals/integrated_evals.py +5 -45
- cogames/cogs_vs_clips/evals/spanning_evals.py +9 -180
- cogames/cogs_vs_clips/mission.py +187 -146
- cogames/cogs_vs_clips/missions.py +46 -137
- cogames/cogs_vs_clips/procedural.py +8 -8
- cogames/cogs_vs_clips/sites.py +107 -3
- cogames/cogs_vs_clips/stations.py +198 -186
- cogames/cogs_vs_clips/tutorial_missions.py +1 -1
- cogames/cogs_vs_clips/variants.py +25 -476
- cogames/device.py +13 -1
- cogames/{policy/scripted_agent/README.md → docs/SCRIPTED_AGENT.md} +82 -58
- cogames/evaluate.py +18 -30
- cogames/main.py +1434 -243
- cogames/maps/canidate1_1000.map +1 -1
- cogames/maps/canidate1_1000_stations.map +2 -2
- cogames/maps/canidate1_500.map +1 -1
- cogames/maps/canidate1_500_stations.map +2 -2
- cogames/maps/canidate2_1000.map +1 -1
- cogames/maps/canidate2_1000_stations.map +2 -2
- cogames/maps/canidate2_500.map +1 -1
- cogames/maps/canidate2_500_stations.map +2 -2
- cogames/maps/canidate3_1000.map +1 -1
- cogames/maps/canidate3_1000_stations.map +2 -2
- cogames/maps/canidate3_500.map +1 -1
- cogames/maps/canidate3_500_stations.map +2 -2
- cogames/maps/canidate4_500.map +1 -1
- cogames/maps/canidate4_500_stations.map +2 -2
- cogames/maps/cave_base_50.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_agile_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_charge_up.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_near.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_search.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_memory.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_radial.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_radial_hard.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_resource_lab.map +2 -2
- cogames/maps/diagnostic_evals/diagnostic_unclip.map +2 -2
- cogames/maps/evals/eval_balanced_spread.map +9 -5
- cogames/maps/evals/eval_clip_oxygen.map +9 -5
- cogames/maps/evals/eval_collect_resources.map +9 -5
- cogames/maps/evals/eval_collect_resources_hard.map +9 -5
- cogames/maps/evals/eval_collect_resources_medium.map +9 -5
- cogames/maps/evals/eval_divide_and_conquer.map +9 -5
- cogames/maps/evals/eval_energy_starved.map +9 -5
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +9 -5
- cogames/maps/evals/eval_oxygen_bottleneck.map +9 -5
- cogames/maps/evals/eval_single_use_world.map +9 -5
- cogames/maps/evals/extractor_hub_100x100.map +9 -5
- cogames/maps/evals/extractor_hub_30x30.map +9 -5
- cogames/maps/evals/extractor_hub_50x50.map +9 -5
- cogames/maps/evals/extractor_hub_70x70.map +9 -5
- cogames/maps/evals/extractor_hub_80x80.map +9 -5
- cogames/maps/machina_100_stations.map +2 -2
- cogames/maps/machina_200_stations.map +2 -2
- cogames/maps/machina_200_stations_small.map +2 -2
- cogames/maps/machina_eval_exp01.map +2 -2
- cogames/maps/machina_eval_template_large.map +2 -2
- cogames/maps/machinatrainer4agents.map +2 -2
- cogames/maps/machinatrainer4agentsbase.map +2 -2
- cogames/maps/machinatrainerbig.map +2 -2
- cogames/maps/machinatrainersmall.map +2 -2
- cogames/maps/planky_evals/aligner_avoid_aoe.map +28 -0
- cogames/maps/planky_evals/aligner_full_cycle.map +28 -0
- cogames/maps/planky_evals/aligner_gear.map +24 -0
- cogames/maps/planky_evals/aligner_hearts.map +24 -0
- cogames/maps/planky_evals/aligner_junction.map +26 -0
- cogames/maps/planky_evals/exploration_distant.map +28 -0
- cogames/maps/planky_evals/maze.map +32 -0
- cogames/maps/planky_evals/miner_best_resource.map +26 -0
- cogames/maps/planky_evals/miner_deposit.map +24 -0
- cogames/maps/planky_evals/miner_extract.map +26 -0
- cogames/maps/planky_evals/miner_full_cycle.map +28 -0
- cogames/maps/planky_evals/miner_gear.map +24 -0
- cogames/maps/planky_evals/multi_role.map +28 -0
- cogames/maps/planky_evals/resource_chain.map +30 -0
- cogames/maps/planky_evals/scout_explore.map +32 -0
- cogames/maps/planky_evals/scout_gear.map +24 -0
- cogames/maps/planky_evals/scrambler_full_cycle.map +28 -0
- cogames/maps/planky_evals/scrambler_gear.map +24 -0
- cogames/maps/planky_evals/scrambler_target.map +26 -0
- cogames/maps/planky_evals/stuck_corridor.map +32 -0
- cogames/maps/planky_evals/survive_retreat.map +26 -0
- cogames/maps/training_facility_clipped.map +2 -2
- cogames/maps/training_facility_open_1.map +2 -2
- cogames/maps/training_facility_open_2.map +2 -2
- cogames/maps/training_facility_open_3.map +2 -2
- cogames/maps/training_facility_tight_4.map +2 -2
- cogames/maps/training_facility_tight_5.map +2 -2
- cogames/maps/vanilla_large.map +2 -2
- cogames/maps/vanilla_small.map +2 -2
- cogames/pickup.py +183 -0
- cogames/play.py +166 -33
- cogames/policy/chaos_monkey.py +54 -0
- cogames/policy/nim_agents/__init__.py +27 -10
- cogames/policy/nim_agents/agents.py +121 -60
- cogames/policy/nim_agents/thinky_eval.py +35 -222
- cogames/policy/pufferlib_policy.py +67 -32
- cogames/policy/starter_agent.py +184 -0
- cogames/policy/trainable_policy_template.py +4 -1
- cogames/train.py +51 -13
- cogames/verbose.py +2 -2
- cogames-0.3.64.dist-info/METADATA +1842 -0
- cogames-0.3.64.dist-info/RECORD +159 -0
- cogames-0.3.64.dist-info/licenses/LICENSE +21 -0
- cogames-0.3.64.dist-info/top_level.txt +2 -0
- metta_alo/__init__.py +0 -0
- metta_alo/job_specs.py +17 -0
- metta_alo/policy.py +16 -0
- metta_alo/pure_single_episode_runner.py +75 -0
- metta_alo/py.typed +0 -0
- metta_alo/rollout.py +322 -0
- metta_alo/scoring.py +168 -0
- cogames/maps/diagnostic_evals/diagnostic_assembler_near.map +0 -49
- cogames/maps/diagnostic_evals/diagnostic_assembler_search.map +0 -49
- cogames/maps/diagnostic_evals/diagnostic_assembler_search_hard.map +0 -89
- cogames/policy/nim_agents/common.nim +0 -887
- cogames/policy/nim_agents/install.sh +0 -1
- cogames/policy/nim_agents/ladybug_agent.nim +0 -984
- cogames/policy/nim_agents/nim_agents.nim +0 -55
- cogames/policy/nim_agents/nim_agents.nims +0 -14
- cogames/policy/nim_agents/nimby.lock +0 -3
- cogames/policy/nim_agents/racecar_agents.nim +0 -884
- cogames/policy/nim_agents/random_agents.nim +0 -68
- cogames/policy/nim_agents/test_agents.py +0 -53
- cogames/policy/nim_agents/thinky_agents.nim +0 -717
- cogames/policy/scripted_agent/baseline_agent.py +0 -1049
- cogames/policy/scripted_agent/demo_policy.py +0 -244
- cogames/policy/scripted_agent/pathfinding.py +0 -126
- cogames/policy/scripted_agent/starter_agent.py +0 -136
- cogames/policy/scripted_agent/types.py +0 -235
- cogames/policy/scripted_agent/unclipping_agent.py +0 -476
- cogames/policy/scripted_agent/utils.py +0 -385
- cogames-0.3.49.dist-info/METADATA +0 -406
- cogames-0.3.49.dist-info/RECORD +0 -136
- cogames-0.3.49.dist-info/top_level.txt +0 -1
- {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/WHEEL +0 -0
- {cogames-0.3.49.dist-info → cogames-0.3.64.dist-info}/entry_points.txt +0 -0
|
@@ -1,385 +0,0 @@
|
|
|
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, Union
|
|
10
|
-
|
|
11
|
-
from mettagrid.simulator import Action
|
|
12
|
-
from mettagrid.simulator.interface import AgentObservation
|
|
13
|
-
|
|
14
|
-
from .types import ObjectState, ParsedObservation, SimpleAgentState
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def manhattan_distance(pos1: tuple[int, int], pos2: tuple[int, int]) -> int:
|
|
18
|
-
"""Calculate Manhattan distance between two positions."""
|
|
19
|
-
return abs(pos1[0] - pos2[0]) + abs(pos1[1] - pos2[1])
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def is_adjacent(pos1: tuple[int, int], pos2: tuple[int, int]) -> bool:
|
|
23
|
-
"""Check if two positions are adjacent (4-way cardinal directions)."""
|
|
24
|
-
dr = abs(pos1[0] - pos2[0])
|
|
25
|
-
dc = abs(pos1[1] - pos2[1])
|
|
26
|
-
return (dr == 1 and dc == 0) or (dr == 0 and dc == 1)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def is_within_bounds(pos: tuple[int, int], map_height: int, map_width: int) -> bool:
|
|
30
|
-
"""Check if a position is within map bounds."""
|
|
31
|
-
r, c = pos
|
|
32
|
-
return 0 <= r < map_height and 0 <= c < map_width
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def get_cardinal_neighbors(pos: tuple[int, int]) -> list[tuple[int, int]]:
|
|
36
|
-
"""Get all 4-way cardinal neighbor positions."""
|
|
37
|
-
r, c = pos
|
|
38
|
-
return [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def is_wall(obj_name: str) -> bool:
|
|
42
|
-
"""Check if an object name represents a wall or obstacle."""
|
|
43
|
-
return "wall" in obj_name or "#" in obj_name or obj_name in {"wall", "obstacle"}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def is_floor(obj_name: str) -> bool:
|
|
47
|
-
"""Check if an object name represents floor (passable empty space)."""
|
|
48
|
-
# Environment returns empty string for empty cells
|
|
49
|
-
return obj_name in {"floor", ""}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def is_station(obj_name: str, station: str) -> bool:
|
|
53
|
-
"""Check if an object name contains a specific station type."""
|
|
54
|
-
return station in obj_name
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def position_to_direction(from_pos: tuple[int, int], to_pos: tuple[int, int]) -> str | None:
|
|
58
|
-
"""
|
|
59
|
-
Convert adjacent positions to a cardinal direction name.
|
|
60
|
-
|
|
61
|
-
Returns: "north", "south", "east", "west", or None if not adjacent.
|
|
62
|
-
"""
|
|
63
|
-
dr = to_pos[0] - from_pos[0]
|
|
64
|
-
dc = to_pos[1] - from_pos[1]
|
|
65
|
-
|
|
66
|
-
if dr == -1 and dc == 0:
|
|
67
|
-
return "north"
|
|
68
|
-
elif dr == 1 and dc == 0:
|
|
69
|
-
return "south"
|
|
70
|
-
elif dr == 0 and dc == 1:
|
|
71
|
-
return "east"
|
|
72
|
-
elif dr == 0 and dc == -1:
|
|
73
|
-
return "west"
|
|
74
|
-
return None
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def process_feature_at_position(
|
|
78
|
-
position_features: dict[tuple[int, int], dict[str, Union[int, list[int], dict[str, int]]]],
|
|
79
|
-
pos: tuple[int, int],
|
|
80
|
-
feature_name: str,
|
|
81
|
-
value: int,
|
|
82
|
-
*,
|
|
83
|
-
spatial_feature_names: set[str],
|
|
84
|
-
agent_feature_key_by_name: dict[str, str],
|
|
85
|
-
protocol_input_prefix: str,
|
|
86
|
-
protocol_output_prefix: str,
|
|
87
|
-
) -> None:
|
|
88
|
-
"""Process a single observation feature and add it to position_features."""
|
|
89
|
-
if pos not in position_features:
|
|
90
|
-
position_features[pos] = {}
|
|
91
|
-
|
|
92
|
-
# Handle spatial features (tag, cooldown, etc.)
|
|
93
|
-
if feature_name in spatial_feature_names:
|
|
94
|
-
# Tag: collect all tags as a list (objects can have multiple tags)
|
|
95
|
-
if feature_name == "tag":
|
|
96
|
-
tags = position_features[pos].setdefault("tags", [])
|
|
97
|
-
if isinstance(tags, list):
|
|
98
|
-
tags.append(value)
|
|
99
|
-
return
|
|
100
|
-
# Other spatial features are single values
|
|
101
|
-
position_features[pos][feature_name] = value
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
# Handle agent features (agent:group -> agent_group, etc.)
|
|
105
|
-
agent_feature_key = agent_feature_key_by_name.get(feature_name)
|
|
106
|
-
if agent_feature_key is not None:
|
|
107
|
-
position_features[pos][agent_feature_key] = value
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
# Handle protocol features (recipes)
|
|
111
|
-
if feature_name.startswith(protocol_input_prefix):
|
|
112
|
-
resource = feature_name[len(protocol_input_prefix) :]
|
|
113
|
-
inputs = position_features[pos].setdefault("protocol_inputs", {})
|
|
114
|
-
if isinstance(inputs, dict):
|
|
115
|
-
inputs[resource] = value
|
|
116
|
-
return
|
|
117
|
-
|
|
118
|
-
if feature_name.startswith(protocol_output_prefix):
|
|
119
|
-
resource = feature_name[len(protocol_output_prefix) :]
|
|
120
|
-
outputs = position_features[pos].setdefault("protocol_outputs", {})
|
|
121
|
-
if isinstance(outputs, dict):
|
|
122
|
-
outputs[resource] = value
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def create_object_state(
|
|
127
|
-
features: dict[str, Union[int, list[int], dict[str, int]]],
|
|
128
|
-
*,
|
|
129
|
-
tag_names: dict[int, str],
|
|
130
|
-
) -> ObjectState:
|
|
131
|
-
"""Create an ObjectState from collected features.
|
|
132
|
-
|
|
133
|
-
Note: Objects can have multiple tags (e.g., "wall" + "green" vibe).
|
|
134
|
-
We use the first tag as the primary object name.
|
|
135
|
-
"""
|
|
136
|
-
# Get tags list (now stored as "tags" instead of "tag")
|
|
137
|
-
tags_value = features.get("tags", [])
|
|
138
|
-
if isinstance(tags_value, list):
|
|
139
|
-
tag_ids = list(tags_value)
|
|
140
|
-
elif isinstance(tags_value, int):
|
|
141
|
-
tag_ids = [tags_value]
|
|
142
|
-
else:
|
|
143
|
-
tag_ids = []
|
|
144
|
-
|
|
145
|
-
# Use first tag as primary object name
|
|
146
|
-
if tag_ids:
|
|
147
|
-
primary_tag_id = tag_ids[0]
|
|
148
|
-
obj_name = tag_names.get(primary_tag_id, f"unknown_tag_{primary_tag_id}")
|
|
149
|
-
else:
|
|
150
|
-
obj_name = "unknown"
|
|
151
|
-
|
|
152
|
-
# Helper to safely extract int values
|
|
153
|
-
def get_int(key: str, default: int) -> int:
|
|
154
|
-
val = features.get(key, default)
|
|
155
|
-
return int(val) if isinstance(val, int) else default
|
|
156
|
-
|
|
157
|
-
# Helper to safely extract dict values
|
|
158
|
-
def get_dict(key: str) -> dict[str, int]:
|
|
159
|
-
val = features.get(key, {})
|
|
160
|
-
return dict(val) if isinstance(val, dict) else {}
|
|
161
|
-
|
|
162
|
-
return ObjectState(
|
|
163
|
-
name=obj_name,
|
|
164
|
-
cooldown_remaining=get_int("cooldown_remaining", 0),
|
|
165
|
-
clipped=get_int("clipped", 0),
|
|
166
|
-
remaining_uses=get_int("remaining_uses", 999),
|
|
167
|
-
protocol_inputs=get_dict("protocol_inputs"),
|
|
168
|
-
protocol_outputs=get_dict("protocol_outputs"),
|
|
169
|
-
agent_group=get_int("agent_group", -1),
|
|
170
|
-
agent_frozen=get_int("agent_frozen", 0),
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
def read_inventory_from_obs(
|
|
175
|
-
state: SimpleAgentState,
|
|
176
|
-
obs: AgentObservation,
|
|
177
|
-
*,
|
|
178
|
-
obs_hr: int,
|
|
179
|
-
obs_wr: int,
|
|
180
|
-
) -> None:
|
|
181
|
-
"""Read inventory from observation tokens at center cell and update state."""
|
|
182
|
-
inv = {}
|
|
183
|
-
center_r, center_c = obs_hr, obs_wr
|
|
184
|
-
for tok in obs.tokens:
|
|
185
|
-
if tok.location == (center_r, center_c):
|
|
186
|
-
feature_name = tok.feature.name
|
|
187
|
-
if feature_name.startswith("inv:"):
|
|
188
|
-
resource_name = feature_name[4:] # Remove "inv:" prefix
|
|
189
|
-
inv[resource_name] = tok.value
|
|
190
|
-
|
|
191
|
-
state.energy = inv.get("energy", 0)
|
|
192
|
-
state.carbon = inv.get("carbon", 0)
|
|
193
|
-
state.oxygen = inv.get("oxygen", 0)
|
|
194
|
-
state.germanium = inv.get("germanium", 0)
|
|
195
|
-
state.silicon = inv.get("silicon", 0)
|
|
196
|
-
state.hearts = inv.get("heart", 0)
|
|
197
|
-
state.decoder = inv.get("decoder", 0)
|
|
198
|
-
state.modulator = inv.get("modulator", 0)
|
|
199
|
-
state.resonator = inv.get("resonator", 0)
|
|
200
|
-
state.scrambler = inv.get("scrambler", 0)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def parse_observation(
|
|
204
|
-
state: SimpleAgentState,
|
|
205
|
-
obs: AgentObservation,
|
|
206
|
-
*,
|
|
207
|
-
obs_hr: int,
|
|
208
|
-
obs_wr: int,
|
|
209
|
-
spatial_feature_names: set[str],
|
|
210
|
-
agent_feature_key_by_name: dict[str, str],
|
|
211
|
-
protocol_input_prefix: str,
|
|
212
|
-
protocol_output_prefix: str,
|
|
213
|
-
tag_names: dict[int, str],
|
|
214
|
-
debug: bool = False,
|
|
215
|
-
) -> ParsedObservation:
|
|
216
|
-
"""Parse token-based observation into structured format.
|
|
217
|
-
|
|
218
|
-
AgentObservation with tokens (ObservationToken list)
|
|
219
|
-
- Inventory is obtained via agent.inventory (not parsed here)
|
|
220
|
-
- Only spatial features are parsed from observations
|
|
221
|
-
|
|
222
|
-
Converts egocentric spatial coordinates to world coordinates using agent position.
|
|
223
|
-
Agent position (agent_row, agent_col) comes from simulation.grid_objects().
|
|
224
|
-
"""
|
|
225
|
-
# First pass: collect all spatial features by position
|
|
226
|
-
position_features: dict[tuple[int, int], dict[str, Union[int, list[int], dict[str, int]]]] = {}
|
|
227
|
-
|
|
228
|
-
for tok in obs.tokens:
|
|
229
|
-
obs_r, obs_c = tok.location
|
|
230
|
-
feature_name = tok.feature.name
|
|
231
|
-
value = tok.value
|
|
232
|
-
|
|
233
|
-
# Skip center location - that's inventory/global obs, obtained via agent.inventory
|
|
234
|
-
if obs_r == obs_hr and obs_c == obs_wr:
|
|
235
|
-
continue
|
|
236
|
-
|
|
237
|
-
# Convert observation-relative coords to world coords
|
|
238
|
-
if state.row >= 0 and state.col >= 0:
|
|
239
|
-
r = obs_r - obs_hr + state.row
|
|
240
|
-
c = obs_c - obs_wr + state.col
|
|
241
|
-
|
|
242
|
-
if 0 <= r < state.map_height and 0 <= c < state.map_width:
|
|
243
|
-
process_feature_at_position(
|
|
244
|
-
position_features,
|
|
245
|
-
(r, c),
|
|
246
|
-
feature_name,
|
|
247
|
-
value,
|
|
248
|
-
spatial_feature_names=spatial_feature_names,
|
|
249
|
-
agent_feature_key_by_name=agent_feature_key_by_name,
|
|
250
|
-
protocol_input_prefix=protocol_input_prefix,
|
|
251
|
-
protocol_output_prefix=protocol_output_prefix,
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
# Second pass: create ObjectState for each position with tags
|
|
255
|
-
nearby_objects = {
|
|
256
|
-
pos: create_object_state(features, tag_names=tag_names)
|
|
257
|
-
for pos, features in position_features.items()
|
|
258
|
-
if "tags" in features # Note: stored as "tags" (plural) to support multiple tags per object
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return ParsedObservation(
|
|
262
|
-
row=state.row,
|
|
263
|
-
col=state.col,
|
|
264
|
-
energy=0, # Inventory obtained via agent.inventory
|
|
265
|
-
carbon=0,
|
|
266
|
-
oxygen=0,
|
|
267
|
-
germanium=0,
|
|
268
|
-
silicon=0,
|
|
269
|
-
hearts=0,
|
|
270
|
-
decoder=0,
|
|
271
|
-
modulator=0,
|
|
272
|
-
resonator=0,
|
|
273
|
-
scrambler=0,
|
|
274
|
-
nearby_objects=nearby_objects,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
def change_vibe_action(
|
|
279
|
-
vibe_name: str,
|
|
280
|
-
*,
|
|
281
|
-
actions: Any, # PolicyEnvInterface.actions
|
|
282
|
-
) -> Action:
|
|
283
|
-
"""
|
|
284
|
-
Return a safe vibe-change action.
|
|
285
|
-
Guard against disabled or single-vibe configurations before issuing the action.
|
|
286
|
-
"""
|
|
287
|
-
from mettagrid.config.vibes import VIBE_BY_NAME
|
|
288
|
-
|
|
289
|
-
change_vibe_cfg = getattr(actions, "change_vibe", None)
|
|
290
|
-
if change_vibe_cfg is None:
|
|
291
|
-
return actions.noop.Noop()
|
|
292
|
-
if not getattr(change_vibe_cfg, "enabled", True):
|
|
293
|
-
return actions.noop.Noop()
|
|
294
|
-
num_vibes = len(getattr(change_vibe_cfg, "vibes", []))
|
|
295
|
-
if num_vibes <= 1:
|
|
296
|
-
return actions.noop.Noop()
|
|
297
|
-
# Raise loudly if the requested vibe isn't registered instead of silently
|
|
298
|
-
# falling back to noop; otherwise config issues become very hard to spot.
|
|
299
|
-
vibe = VIBE_BY_NAME.get(vibe_name)
|
|
300
|
-
if vibe is None:
|
|
301
|
-
raise Exception(f"No valid vibes called {vibe_name}")
|
|
302
|
-
return actions.change_vibe.ChangeVibe(vibe)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
def update_agent_position(
|
|
306
|
-
state: SimpleAgentState,
|
|
307
|
-
*,
|
|
308
|
-
move_deltas: dict[str, tuple[int, int]],
|
|
309
|
-
) -> None:
|
|
310
|
-
"""Update agent position based on last action.
|
|
311
|
-
|
|
312
|
-
Position is tracked relative to origin (starting position), using only movement deltas.
|
|
313
|
-
No dependency on simulation.grid_objects().
|
|
314
|
-
|
|
315
|
-
IMPORTANT: When using objects (extractors, stations), the agent "moves into" them but doesn't
|
|
316
|
-
actually change position. We detect this by checking the using_object_this_step flag.
|
|
317
|
-
"""
|
|
318
|
-
# If last action was a move and we're not using an object, update position
|
|
319
|
-
# We assume the move succeeded unless we were using an object
|
|
320
|
-
if state.last_action and state.last_action.name.startswith("move_") and not state.using_object_this_step:
|
|
321
|
-
# Extract direction from action name (e.g., "move_north" -> "north")
|
|
322
|
-
direction = state.last_action.name[5:] # Remove "move_" prefix
|
|
323
|
-
if direction in move_deltas:
|
|
324
|
-
dr, dc = move_deltas[direction]
|
|
325
|
-
state.row += dr
|
|
326
|
-
state.col += dc
|
|
327
|
-
# Clear the flag for next step
|
|
328
|
-
state.using_object_this_step = False
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
def use_object_at(
|
|
332
|
-
state: SimpleAgentState,
|
|
333
|
-
target_pos: tuple[int, int],
|
|
334
|
-
*,
|
|
335
|
-
actions: Any, # PolicyEnvInterface.actions
|
|
336
|
-
move_deltas: dict[str, tuple[int, int]],
|
|
337
|
-
using_for: str = "",
|
|
338
|
-
) -> Action:
|
|
339
|
-
"""Use an object by moving into its cell. Sets a flag so position tracking knows not to update.
|
|
340
|
-
|
|
341
|
-
This is the generic "move into to use" action for extractors, assemblers, chests, chargers, etc.
|
|
342
|
-
The 'using_for' parameter is used for tracking what we're using (e.g., 'extractor', 'assembler').
|
|
343
|
-
"""
|
|
344
|
-
action = move_into_cell(state, target_pos, actions=actions, move_deltas=move_deltas)
|
|
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
|
-
*,
|
|
356
|
-
actions: Any, # PolicyEnvInterface.actions
|
|
357
|
-
move_deltas: dict[str, tuple[int, int]],
|
|
358
|
-
) -> Action:
|
|
359
|
-
"""Return the action that attempts to step into the target cell.
|
|
360
|
-
|
|
361
|
-
Checks for agent occupancy before moving to avoid collisions.
|
|
362
|
-
"""
|
|
363
|
-
|
|
364
|
-
tr, tc = target
|
|
365
|
-
if state.row == tr and state.col == tc:
|
|
366
|
-
return actions.noop.Noop()
|
|
367
|
-
dr = tr - state.row
|
|
368
|
-
dc = tc - state.col
|
|
369
|
-
|
|
370
|
-
# Check if another agent is at the target position
|
|
371
|
-
if (tr, tc) in state.agent_occupancy:
|
|
372
|
-
# Another agent is blocking the target, wait or try alternative
|
|
373
|
-
# For a simple fallback, return noop (caller can handle random direction if needed)
|
|
374
|
-
return actions.noop.Noop()
|
|
375
|
-
|
|
376
|
-
if dr == -1:
|
|
377
|
-
return actions.move.Move("north")
|
|
378
|
-
if dr == 1:
|
|
379
|
-
return actions.move.Move("south")
|
|
380
|
-
if dc == 1:
|
|
381
|
-
return actions.move.Move("east")
|
|
382
|
-
if dc == -1:
|
|
383
|
-
return actions.move.Move("west")
|
|
384
|
-
# Fallback to noop if offsets unexpected
|
|
385
|
-
return actions.noop.Noop()
|