cogames 0.3.64__py3-none-any.whl → 0.3.68__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 +0 -3
- cogames/cli/docsync/docsync.py +7 -1
- cogames/cli/mission.py +68 -53
- cogames/cli/policy.py +26 -10
- cogames/cli/submit.py +128 -142
- cogames/cli/utils.py +5 -0
- cogames/cogs_vs_clips/clip_difficulty.py +57 -0
- cogames/cogs_vs_clips/clips.py +103 -0
- cogames/cogs_vs_clips/cog.py +29 -11
- cogames/cogs_vs_clips/cogsguard_curriculum.py +122 -0
- cogames/cogs_vs_clips/cogsguard_tutorial.py +15 -16
- cogames/cogs_vs_clips/config.py +38 -0
- cogames/cogs_vs_clips/{cogs_vs_clips_mapgen.md → docs/cogs_vs_clips_mapgen.md} +8 -10
- cogames/cogs_vs_clips/evals/README.md +11 -35
- cogames/cogs_vs_clips/evals/cogsguard_evals.py +21 -6
- cogames/cogs_vs_clips/evals/diagnostic_evals.py +13 -101
- cogames/cogs_vs_clips/evals/difficulty_variants.py +16 -28
- cogames/cogs_vs_clips/evals/integrated_evals.py +8 -60
- cogames/cogs_vs_clips/evals/spanning_evals.py +48 -54
- cogames/cogs_vs_clips/mission.py +93 -277
- cogames/cogs_vs_clips/missions.py +17 -27
- cogames/cogs_vs_clips/{cogsguard_reward_variants.py → reward_variants.py} +22 -2
- cogames/cogs_vs_clips/sites.py +41 -30
- cogames/cogs_vs_clips/stations.py +39 -84
- cogames/cogs_vs_clips/team.py +46 -0
- cogames/cogs_vs_clips/{procedural.py → terrain.py} +14 -8
- cogames/cogs_vs_clips/variants.py +201 -107
- cogames/cogs_vs_clips/weather.py +52 -0
- cogames/core.py +87 -0
- cogames/docs/SCRIPTED_AGENT.md +3 -3
- cogames/evaluate.py +4 -2
- cogames/main.py +357 -51
- 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 +1 -1
- 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 +6 -6
- cogames/maps/diagnostic_evals/diagnostic_charge_up_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation1_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation2_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_navigation3_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_near.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_chest_search_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_extract_lab_hard.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory.map +6 -6
- cogames/maps/diagnostic_evals/diagnostic_memory_hard.map +6 -6
- 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 +6 -6
- cogames/maps/diagnostic_evals/diagnostic_unclip.map +6 -6
- cogames/maps/evals/eval_balanced_spread.map +6 -6
- cogames/maps/evals/eval_clip_oxygen.map +6 -6
- cogames/maps/evals/eval_collect_resources.map +6 -6
- cogames/maps/evals/eval_collect_resources_hard.map +6 -6
- cogames/maps/evals/eval_collect_resources_medium.map +6 -6
- cogames/maps/evals/eval_divide_and_conquer.map +6 -6
- cogames/maps/evals/eval_energy_starved.map +6 -6
- cogames/maps/evals/eval_multi_coordinated_collect_hard.map +6 -6
- cogames/maps/evals/eval_oxygen_bottleneck.map +6 -6
- cogames/maps/evals/eval_single_use_world.map +6 -6
- cogames/maps/evals/extractor_hub_100x100.map +6 -6
- cogames/maps/evals/extractor_hub_30x30.map +6 -6
- cogames/maps/evals/extractor_hub_50x50.map +6 -6
- cogames/maps/evals/extractor_hub_70x70.map +6 -6
- cogames/maps/evals/extractor_hub_80x80.map +6 -6
- 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 +6 -6
- cogames/maps/planky_evals/aligner_full_cycle.map +6 -6
- cogames/maps/planky_evals/aligner_gear.map +6 -6
- cogames/maps/planky_evals/aligner_hearts.map +6 -6
- cogames/maps/planky_evals/aligner_junction.map +6 -6
- cogames/maps/planky_evals/exploration_distant.map +6 -6
- cogames/maps/planky_evals/maze.map +6 -6
- cogames/maps/planky_evals/miner_best_resource.map +6 -6
- cogames/maps/planky_evals/miner_deposit.map +6 -6
- cogames/maps/planky_evals/miner_extract.map +6 -6
- cogames/maps/planky_evals/miner_full_cycle.map +6 -6
- cogames/maps/planky_evals/miner_gear.map +6 -6
- cogames/maps/planky_evals/multi_role.map +6 -6
- cogames/maps/planky_evals/resource_chain.map +6 -6
- cogames/maps/planky_evals/scout_explore.map +6 -6
- cogames/maps/planky_evals/scout_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_full_cycle.map +6 -6
- cogames/maps/planky_evals/scrambler_gear.map +6 -6
- cogames/maps/planky_evals/scrambler_target.map +6 -6
- cogames/maps/planky_evals/stuck_corridor.map +6 -6
- cogames/maps/planky_evals/survive_retreat.map +6 -6
- 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 +6 -5
- cogames/play.py +14 -16
- cogames/policy/nim_agents/__init__.py +0 -2
- cogames/policy/nim_agents/agents.py +0 -11
- cogames/policy/starter_agent.py +4 -1
- cogames/verbose.py +2 -2
- {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/METADATA +45 -29
- cogames-0.3.68.dist-info/RECORD +160 -0
- metta_alo/scoring.py +7 -7
- cogames/cogs_vs_clips/mission_utils.py +0 -19
- cogames/cogs_vs_clips/tutorial_missions.py +0 -25
- cogames-0.3.64.dist-info/RECORD +0 -159
- metta_alo/job_specs.py +0 -17
- metta_alo/policy.py +0 -16
- metta_alo/pure_single_episode_runner.py +0 -75
- metta_alo/rollout.py +0 -322
- {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/WHEEL +0 -0
- {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/entry_points.txt +0 -0
- {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/licenses/LICENSE +0 -0
- {cogames-0.3.64.dist-info → cogames-0.3.68.dist-info}/top_level.txt +0 -0
|
@@ -1,95 +1,189 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import TYPE_CHECKING, override
|
|
4
|
+
|
|
5
|
+
from pydantic import Field
|
|
6
|
+
|
|
7
|
+
from cogames.cogs_vs_clips.config import CvCConfig
|
|
3
8
|
from cogames.cogs_vs_clips.evals.difficulty_variants import DIFFICULTY_VARIANTS
|
|
4
|
-
from cogames.cogs_vs_clips.
|
|
5
|
-
from cogames.
|
|
6
|
-
from mettagrid.config.
|
|
7
|
-
from mettagrid.config.game_value import stat
|
|
8
|
-
from mettagrid.config.reward_config import reward
|
|
9
|
+
from cogames.cogs_vs_clips.terrain import BaseHubVariant, MachinaArenaVariant
|
|
10
|
+
from cogames.core import CoGameMissionVariant
|
|
11
|
+
from mettagrid.config.mettagrid_config import MettaGridConfig
|
|
9
12
|
from mettagrid.map_builder.map_builder import MapBuilderConfig
|
|
10
13
|
from mettagrid.mapgen.mapgen import MapGen
|
|
11
14
|
from mettagrid.mapgen.scenes.base_hub import DEFAULT_EXTRACTORS as HUB_EXTRACTORS
|
|
12
15
|
from mettagrid.mapgen.scenes.building_distributions import DistributionConfig, DistributionType
|
|
13
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from cogames.cogs_vs_clips.mission import CvCMission
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _apply_clips_settings(
|
|
22
|
+
mission: CvCMission,
|
|
23
|
+
*,
|
|
24
|
+
initial_clips_start: int | None = None,
|
|
25
|
+
initial_clips_spots: int | None = None,
|
|
26
|
+
scramble_start: int | None = None,
|
|
27
|
+
scramble_interval: int | None = None,
|
|
28
|
+
scramble_radius: int | None = None,
|
|
29
|
+
align_start: int | None = None,
|
|
30
|
+
align_interval: int | None = None,
|
|
31
|
+
align_radius: int | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
clips = mission.clips
|
|
34
|
+
if initial_clips_start is not None:
|
|
35
|
+
clips.initial_clips_start = initial_clips_start
|
|
36
|
+
if initial_clips_spots is not None:
|
|
37
|
+
clips.initial_clips_spots = initial_clips_spots
|
|
38
|
+
if scramble_start is not None:
|
|
39
|
+
clips.scramble_start = scramble_start
|
|
40
|
+
if scramble_interval is not None:
|
|
41
|
+
clips.scramble_interval = scramble_interval
|
|
42
|
+
if scramble_radius is not None:
|
|
43
|
+
clips.scramble_radius = scramble_radius
|
|
44
|
+
if align_start is not None:
|
|
45
|
+
clips.align_start = align_start
|
|
46
|
+
if align_interval is not None:
|
|
47
|
+
clips.align_interval = align_interval
|
|
48
|
+
if align_radius is not None:
|
|
49
|
+
clips.align_radius = align_radius
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class NumCogsVariant(CoGameMissionVariant):
|
|
53
|
+
name: str = "num_cogs"
|
|
54
|
+
description: str = "Set the number of cogs for the mission."
|
|
55
|
+
num_cogs: int
|
|
14
56
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
57
|
+
@override
|
|
58
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
59
|
+
if self.num_cogs < mission.site.min_cogs or self.num_cogs > mission.site.max_cogs:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Invalid number of cogs for {mission.site.name}: {self.num_cogs}. "
|
|
62
|
+
+ f"Must be between {mission.site.min_cogs} and {mission.site.max_cogs}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
mission.num_cogs = self.num_cogs
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ClipsEasyVariant(CoGameMissionVariant):
|
|
69
|
+
name: str = "clips_easy"
|
|
70
|
+
description: str = "Slow clips expansion with late pressure."
|
|
18
71
|
|
|
19
72
|
@override
|
|
20
|
-
def modify_mission(self, mission):
|
|
21
|
-
|
|
22
|
-
|
|
73
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
74
|
+
_apply_clips_settings(
|
|
75
|
+
mission,
|
|
76
|
+
initial_clips_start=50,
|
|
77
|
+
initial_clips_spots=1,
|
|
78
|
+
scramble_start=250,
|
|
79
|
+
scramble_interval=250,
|
|
80
|
+
scramble_radius=15,
|
|
81
|
+
align_start=300,
|
|
82
|
+
align_interval=250,
|
|
83
|
+
align_radius=15,
|
|
84
|
+
)
|
|
23
85
|
|
|
24
86
|
|
|
25
|
-
class
|
|
26
|
-
name: str = "
|
|
27
|
-
description: str = "
|
|
87
|
+
class ClipsMediumVariant(CoGameMissionVariant):
|
|
88
|
+
name: str = "clips_medium"
|
|
89
|
+
description: str = "Baseline clips pressure (Machina1 default)."
|
|
28
90
|
|
|
29
91
|
@override
|
|
30
|
-
def modify_mission(self, mission):
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
93
|
+
_apply_clips_settings(
|
|
94
|
+
mission,
|
|
95
|
+
initial_clips_start=10,
|
|
96
|
+
initial_clips_spots=1,
|
|
97
|
+
scramble_start=50,
|
|
98
|
+
scramble_interval=100,
|
|
99
|
+
scramble_radius=25,
|
|
100
|
+
align_start=100,
|
|
101
|
+
align_interval=100,
|
|
102
|
+
align_radius=25,
|
|
103
|
+
)
|
|
33
104
|
|
|
34
105
|
|
|
35
|
-
class
|
|
36
|
-
name: str = "
|
|
37
|
-
description: str = "
|
|
106
|
+
class ClipsHardVariant(CoGameMissionVariant):
|
|
107
|
+
name: str = "clips_hard"
|
|
108
|
+
description: str = "Fast clips pressure with wider influence."
|
|
38
109
|
|
|
39
110
|
@override
|
|
40
|
-
def modify_mission(self, mission):
|
|
41
|
-
|
|
42
|
-
|
|
111
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
112
|
+
_apply_clips_settings(
|
|
113
|
+
mission,
|
|
114
|
+
initial_clips_start=5,
|
|
115
|
+
initial_clips_spots=2,
|
|
116
|
+
scramble_start=25,
|
|
117
|
+
scramble_interval=50,
|
|
118
|
+
scramble_radius=35,
|
|
119
|
+
align_start=50,
|
|
120
|
+
align_interval=50,
|
|
121
|
+
align_radius=35,
|
|
122
|
+
)
|
|
43
123
|
|
|
44
124
|
|
|
45
|
-
class
|
|
46
|
-
name: str = "
|
|
47
|
-
description: str = "
|
|
125
|
+
class ClipsWaveOnlyVariant(CoGameMissionVariant):
|
|
126
|
+
name: str = "clips_wave_only"
|
|
127
|
+
description: str = "Initial clips wave only, no further spread."
|
|
48
128
|
|
|
49
129
|
@override
|
|
50
|
-
def modify_mission(self, mission):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
130
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
131
|
+
disable_start = mission.max_steps + 1
|
|
132
|
+
_apply_clips_settings(
|
|
133
|
+
mission,
|
|
134
|
+
initial_clips_start=10,
|
|
135
|
+
initial_clips_spots=3,
|
|
136
|
+
scramble_start=disable_start,
|
|
137
|
+
scramble_interval=disable_start,
|
|
138
|
+
align_start=disable_start,
|
|
139
|
+
align_interval=disable_start,
|
|
140
|
+
scramble_radius=25,
|
|
141
|
+
align_radius=25,
|
|
142
|
+
)
|
|
56
143
|
|
|
57
144
|
|
|
58
|
-
class
|
|
59
|
-
name: str = "
|
|
60
|
-
description: str = "
|
|
145
|
+
class DarkSideVariant(CoGameMissionVariant):
|
|
146
|
+
name: str = "dark_side"
|
|
147
|
+
description: str = "You're on the dark side of the asteroid. You recharge slower."
|
|
61
148
|
|
|
62
149
|
@override
|
|
63
|
-
def modify_mission(self, mission):
|
|
64
|
-
|
|
65
|
-
mission.
|
|
66
|
-
|
|
150
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
151
|
+
mission.weather.day_deltas = {"solar": 0}
|
|
152
|
+
mission.weather.night_deltas = {"solar": 0}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class SuperChargedVariant(CoGameMissionVariant):
|
|
156
|
+
name: str = "super_charged"
|
|
157
|
+
description: str = "The sun is shining on you. You recharge faster."
|
|
158
|
+
|
|
159
|
+
@override
|
|
160
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
161
|
+
mission.weather.day_deltas = {k: v + 2 for k, v in mission.weather.day_deltas.items()}
|
|
162
|
+
mission.weather.night_deltas = {k: v + 2 for k, v in mission.weather.night_deltas.items()}
|
|
67
163
|
|
|
68
164
|
|
|
69
|
-
class
|
|
70
|
-
name: str = "
|
|
71
|
-
description: str = "
|
|
165
|
+
class EnergizedVariant(CoGameMissionVariant):
|
|
166
|
+
name: str = "energized"
|
|
167
|
+
description: str = "Max energy and full regen so agents never run dry."
|
|
72
168
|
|
|
73
169
|
@override
|
|
74
|
-
def
|
|
75
|
-
|
|
170
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
171
|
+
mission.cog.energy_limit = max(mission.cog.energy_limit, 255)
|
|
172
|
+
mission.weather.day_deltas = {"solar": 255}
|
|
173
|
+
mission.weather.night_deltas = {"solar": 255}
|
|
76
174
|
|
|
77
175
|
|
|
78
|
-
class Small50Variant(
|
|
176
|
+
class Small50Variant(CoGameMissionVariant):
|
|
79
177
|
name: str = "small_50"
|
|
80
178
|
description: str = "Set map size to 50x50 for quick runs."
|
|
81
179
|
|
|
82
180
|
def modify_env(self, mission, env) -> None:
|
|
83
181
|
map_builder = env.game.map_builder
|
|
84
|
-
# Only set width/height if instance is a SceneConfig, not a MapBuilderConfig
|
|
85
|
-
# When instance is a MapBuilderConfig, width and height must be None
|
|
86
182
|
if isinstance(map_builder, MapGen.Config) and isinstance(map_builder.instance, MapBuilderConfig):
|
|
87
|
-
# Skip setting width/height for MapBuilderConfig instances
|
|
88
183
|
return
|
|
89
184
|
env.game.map_builder = map_builder.model_copy(update={"width": 50, "height": 50})
|
|
90
185
|
|
|
91
186
|
|
|
92
|
-
# Biome variants (weather) for procedural maps
|
|
93
187
|
class DesertVariant(MachinaArenaVariant):
|
|
94
188
|
name: str = "desert"
|
|
95
189
|
description: str = "The desert sands make navigation challenging."
|
|
@@ -117,7 +211,6 @@ class CityVariant(MachinaArenaVariant):
|
|
|
117
211
|
def modify_node(self, node):
|
|
118
212
|
node.biome_weights = {"city": 1.0, "caves": 0.0, "desert": 0.0, "forest": 0.0}
|
|
119
213
|
node.base_biome = "city"
|
|
120
|
-
# Fill almost the entire map with the city layer
|
|
121
214
|
node.density_scale = 1.0
|
|
122
215
|
node.biome_count = 1
|
|
123
216
|
node.max_biome_zone_fraction = 0.95
|
|
@@ -140,32 +233,29 @@ class DistantResourcesVariant(MachinaArenaVariant):
|
|
|
140
233
|
|
|
141
234
|
@override
|
|
142
235
|
def modify_node(self, node):
|
|
143
|
-
# Bias buildings toward the map edges using bimodal clusters centered at
|
|
144
236
|
node.building_coverage = 0.01
|
|
145
237
|
|
|
146
238
|
vertical_edges = DistributionConfig(
|
|
147
239
|
type=DistributionType.BIMODAL,
|
|
148
|
-
center1_x=0.92,
|
|
240
|
+
center1_x=0.92,
|
|
149
241
|
center1_y=0.08,
|
|
150
|
-
center2_x=0.08,
|
|
242
|
+
center2_x=0.08,
|
|
151
243
|
center2_y=0.92,
|
|
152
244
|
cluster_std=0.18,
|
|
153
245
|
)
|
|
154
246
|
horizontal_edges = DistributionConfig(
|
|
155
247
|
type=DistributionType.BIMODAL,
|
|
156
|
-
center1_x=0.08,
|
|
248
|
+
center1_x=0.08,
|
|
157
249
|
center1_y=0.08,
|
|
158
|
-
center2_x=0.92,
|
|
250
|
+
center2_x=0.92,
|
|
159
251
|
center2_y=0.92,
|
|
160
252
|
cluster_std=0.18,
|
|
161
253
|
)
|
|
162
254
|
|
|
163
|
-
# Apply edge-biased distributions to extractors; other buildings follow the global distribution
|
|
164
255
|
names = list(self.building_names)
|
|
165
256
|
node.building_distributions = {
|
|
166
257
|
name: (vertical_edges if i % 2 == 0 else horizontal_edges) for i, name in enumerate(names)
|
|
167
258
|
}
|
|
168
|
-
# Fallback for any unspecified building types
|
|
169
259
|
node.distribution = DistributionConfig(type=DistributionType.UNIFORM)
|
|
170
260
|
|
|
171
261
|
|
|
@@ -180,10 +270,10 @@ class QuadrantBuildingsVariant(MachinaArenaVariant):
|
|
|
180
270
|
|
|
181
271
|
names = list(node.building_names or self.building_names)
|
|
182
272
|
centers = [
|
|
183
|
-
(0.25, 0.25),
|
|
184
|
-
(0.75, 0.25),
|
|
185
|
-
(0.25, 0.75),
|
|
186
|
-
(0.75, 0.75),
|
|
273
|
+
(0.25, 0.25),
|
|
274
|
+
(0.75, 0.25),
|
|
275
|
+
(0.25, 0.75),
|
|
276
|
+
(0.75, 0.75),
|
|
187
277
|
]
|
|
188
278
|
dists: dict[str, DistributionConfig] = {}
|
|
189
279
|
for i, name in enumerate(names):
|
|
@@ -206,8 +296,6 @@ class SingleResourceUniformVariant(MachinaArenaVariant):
|
|
|
206
296
|
|
|
207
297
|
@override
|
|
208
298
|
def modify_node(self, node):
|
|
209
|
-
# Resolve resource to a concrete building name
|
|
210
|
-
# Restrict building set to only the chosen building and enforce uniform distribution
|
|
211
299
|
node.building_names = [self.building_name]
|
|
212
300
|
node.building_weights = {self.building_name: 1.0}
|
|
213
301
|
node.building_distributions = None
|
|
@@ -217,12 +305,10 @@ class SingleResourceUniformVariant(MachinaArenaVariant):
|
|
|
217
305
|
class EmptyBaseVariant(BaseHubVariant):
|
|
218
306
|
name: str = "empty_base"
|
|
219
307
|
description: str = "Base hub with extractors removed from the four corners."
|
|
220
|
-
# Extractor object names to remove, e.g., ["oxygen_extractor"]
|
|
221
308
|
missing: list[str] = list(HUB_EXTRACTORS)
|
|
222
309
|
|
|
223
310
|
@override
|
|
224
311
|
def modify_node(self, node):
|
|
225
|
-
# Use the default extractor order and blank out any that are missing
|
|
226
312
|
missing_set = set(self.missing or [])
|
|
227
313
|
corner_objects = [name if name not in missing_set else "" for name in HUB_EXTRACTORS]
|
|
228
314
|
node.corner_objects = corner_objects
|
|
@@ -230,8 +316,6 @@ class EmptyBaseVariant(BaseHubVariant):
|
|
|
230
316
|
|
|
231
317
|
|
|
232
318
|
class BalancedCornersVariant(MachinaArenaVariant):
|
|
233
|
-
"""Enable corner balancing to ensure fair spawn distances."""
|
|
234
|
-
|
|
235
319
|
name: str = "balanced_corners"
|
|
236
320
|
description: str = "Balance path distances from center to corners for fair spawns."
|
|
237
321
|
balance_tolerance: float = 1.5
|
|
@@ -244,65 +328,75 @@ class BalancedCornersVariant(MachinaArenaVariant):
|
|
|
244
328
|
node.max_balance_shortcuts = self.max_balance_shortcuts
|
|
245
329
|
|
|
246
330
|
|
|
247
|
-
class
|
|
248
|
-
|
|
249
|
-
|
|
331
|
+
class MultiTeamVariant(CoGameMissionVariant):
|
|
332
|
+
"""Split the map into multiple team instances, each with their own hub and resources."""
|
|
333
|
+
|
|
334
|
+
name: str = "multi_team"
|
|
335
|
+
description: str = "Split map into separate team instances with independent hubs."
|
|
336
|
+
num_teams: int = Field(default=2, ge=2, le=2, description="Number of teams (max 2 supported)")
|
|
250
337
|
|
|
251
338
|
@override
|
|
252
|
-
def
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
env.game.
|
|
268
|
-
|
|
339
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
340
|
+
team = next(iter(mission.teams.values()))
|
|
341
|
+
# Each team gets the original agent count; clear num_cogs so total is derived from teams
|
|
342
|
+
original_agents = mission.num_agents
|
|
343
|
+
mission.teams = {
|
|
344
|
+
name: team.model_copy(update={"name": name, "short_name": name, "num_agents": original_agents})
|
|
345
|
+
for name in ["cogs_green", "cogs_blue"][: self.num_teams]
|
|
346
|
+
}
|
|
347
|
+
mission.num_cogs = None
|
|
348
|
+
|
|
349
|
+
def modify_env(self, mission: CvCMission, env: MettaGridConfig) -> None:
|
|
350
|
+
original_builder = env.game.map_builder
|
|
351
|
+
# Shrink inner instance borders so teams are close together
|
|
352
|
+
if isinstance(original_builder, MapGen.Config):
|
|
353
|
+
original_builder.border_width = 1
|
|
354
|
+
env.game.map_builder = MapGen.Config(
|
|
355
|
+
instance=original_builder,
|
|
356
|
+
instances=self.num_teams,
|
|
357
|
+
set_team_by_instance=True,
|
|
358
|
+
instance_names=[t.short_name for t in mission.teams.values()],
|
|
359
|
+
instance_object_remap={
|
|
360
|
+
"c:hub": "{instance_name}:hub",
|
|
361
|
+
"c:chest": "{instance_name}:chest",
|
|
362
|
+
**{f"c:{g}": f"{{instance_name}}:{g}" for g in CvCConfig.GEAR},
|
|
363
|
+
},
|
|
364
|
+
# Connect instances: no added borders, clear walls at boundary
|
|
365
|
+
border_width=0, # No outer border (inner instances have their own)
|
|
366
|
+
instance_border_width=0, # No border between instances
|
|
367
|
+
instance_border_clear_radius=3, # Clear walls near instance boundary
|
|
368
|
+
)
|
|
269
369
|
|
|
270
370
|
|
|
271
|
-
class
|
|
272
|
-
name: str = "
|
|
273
|
-
description: str = "
|
|
371
|
+
class NoClipsVariant(CoGameMissionVariant):
|
|
372
|
+
name: str = "no_clips"
|
|
373
|
+
description: str = "Disable clips behavior entirely."
|
|
274
374
|
|
|
275
375
|
@override
|
|
276
|
-
def
|
|
277
|
-
|
|
278
|
-
env.game.agent.rewards["chest_heart_deposited_by_agent"] = reward(
|
|
279
|
-
stat("chest.heart.deposited_by_agent"), weight=0
|
|
280
|
-
)
|
|
281
|
-
env.game.agent.rewards["chest_heart_amount"] = reward(stat("chest.heart.amount"), weight=1 / num_cogs)
|
|
376
|
+
def modify_mission(self, mission: CvCMission) -> None:
|
|
377
|
+
mission.clips.disabled = True
|
|
282
378
|
|
|
283
379
|
|
|
284
|
-
|
|
285
|
-
VARIANTS: list[MissionVariant] = [
|
|
380
|
+
VARIANTS: list[CoGameMissionVariant] = [
|
|
286
381
|
CavesVariant(),
|
|
287
382
|
CityVariant(),
|
|
288
|
-
CompassVariant(),
|
|
289
383
|
DarkSideVariant(),
|
|
384
|
+
NoClipsVariant(),
|
|
290
385
|
DesertVariant(),
|
|
291
386
|
EmptyBaseVariant(),
|
|
292
387
|
EnergizedVariant(),
|
|
293
388
|
ForestVariant(),
|
|
294
|
-
|
|
389
|
+
MultiTeamVariant(),
|
|
295
390
|
QuadrantBuildingsVariant(),
|
|
296
|
-
RoughTerrainVariant(),
|
|
297
|
-
SharedRewardsVariant(),
|
|
298
391
|
SingleResourceUniformVariant(),
|
|
299
392
|
Small50Variant(),
|
|
300
393
|
SuperChargedVariant(),
|
|
301
|
-
TraderVariant(),
|
|
302
394
|
*DIFFICULTY_VARIANTS,
|
|
303
395
|
]
|
|
304
396
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
397
|
+
HIDDEN_VARIANTS: list[CoGameMissionVariant] = [
|
|
398
|
+
ClipsEasyVariant(),
|
|
399
|
+
ClipsMediumVariant(),
|
|
400
|
+
ClipsHardVariant(),
|
|
401
|
+
ClipsWaveOnlyVariant(),
|
|
308
402
|
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Weather system events for CogsGuard missions.
|
|
2
|
+
|
|
3
|
+
Day/night cycle that applies resource deltas to entities at regular intervals.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from mettagrid.base_config import Config
|
|
9
|
+
from mettagrid.config.event_config import EventConfig, periodic
|
|
10
|
+
from mettagrid.config.mutation import updateTarget
|
|
11
|
+
from mettagrid.config.tag import typeTag
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WeatherConfig(Config):
|
|
15
|
+
"""Configuration for day/night weather cycle."""
|
|
16
|
+
|
|
17
|
+
day_length: int = Field(default=200)
|
|
18
|
+
day_deltas: dict[str, int] = Field(default_factory=lambda: {"solar": 3})
|
|
19
|
+
night_deltas: dict[str, int] = Field(default_factory=lambda: {"solar": 1})
|
|
20
|
+
target_tag: str = Field(default="agent")
|
|
21
|
+
|
|
22
|
+
def events(self, max_steps: int) -> dict[str, EventConfig]:
|
|
23
|
+
"""Create weather events for a mission.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dictionary of event name to EventConfig.
|
|
27
|
+
"""
|
|
28
|
+
events: dict[str, EventConfig] = {}
|
|
29
|
+
tag = typeTag(self.target_tag)
|
|
30
|
+
half = self.day_length // 2
|
|
31
|
+
|
|
32
|
+
def _merge(apply: dict[str, int], reverse: dict[str, int]) -> dict[str, int]:
|
|
33
|
+
keys = set(apply) | set(reverse)
|
|
34
|
+
return {k: apply.get(k, 0) - reverse.get(k, 0) for k in keys}
|
|
35
|
+
|
|
36
|
+
# Dawn: reverse night deltas, apply day deltas
|
|
37
|
+
events["weather_day"] = EventConfig(
|
|
38
|
+
name="weather_day",
|
|
39
|
+
target_tag=tag,
|
|
40
|
+
timesteps=periodic(start=0, period=self.day_length, end=max_steps),
|
|
41
|
+
mutations=[updateTarget(_merge(self.day_deltas, self.night_deltas))],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Dusk: reverse day deltas, apply night deltas
|
|
45
|
+
events["weather_night"] = EventConfig(
|
|
46
|
+
name="weather_night",
|
|
47
|
+
target_tag=tag,
|
|
48
|
+
timesteps=periodic(start=half, period=self.day_length, end=max_steps),
|
|
49
|
+
mutations=[updateTarget(_merge(self.night_deltas, self.day_deltas))],
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return events
|
cogames/core.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Core base classes for CoGame missions and variants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
11
|
+
from mettagrid.base_config import Config
|
|
12
|
+
from mettagrid.config.mettagrid_config import MettaGridConfig
|
|
13
|
+
from mettagrid.map_builder.map_builder import AnyMapBuilderConfig
|
|
14
|
+
|
|
15
|
+
# Type variable for mission types
|
|
16
|
+
TMission = TypeVar("TMission", bound="CoGameMission")
|
|
17
|
+
|
|
18
|
+
MAP_MISSION_DELIMITER = "."
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CoGameMissionVariant(Config, ABC):
|
|
22
|
+
# Note: we could derive the name from the class name automatically, but it would make it
|
|
23
|
+
# harder to find the variant source code based on CLI interactions.
|
|
24
|
+
name: str
|
|
25
|
+
description: str = Field(default="")
|
|
26
|
+
|
|
27
|
+
def modify_mission(self, mission: CoGameMission) -> None:
|
|
28
|
+
# Override this method to modify the mission.
|
|
29
|
+
# Variants are allowed to modify the mission in-place - it's guaranteed to be a one-time only instance.
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
def modify_env(self, mission: CoGameMission, env: MettaGridConfig) -> None:
|
|
33
|
+
# Override this method to modify the produced environment.
|
|
34
|
+
# Variants are allowed to modify the environment in-place.
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
def compat(self, mission: CoGameMission) -> bool:
|
|
38
|
+
"""Check if this variant is compatible with the given mission.
|
|
39
|
+
|
|
40
|
+
Returns True if the variant can be safely applied to the mission.
|
|
41
|
+
Override this method to add compatibility checks.
|
|
42
|
+
"""
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def apply(self, mission: TMission) -> TMission:
|
|
46
|
+
mission = mission.model_copy(deep=True)
|
|
47
|
+
mission.variants.append(self)
|
|
48
|
+
self.modify_mission(mission)
|
|
49
|
+
return mission
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class CoGameSite(Config):
|
|
53
|
+
name: str
|
|
54
|
+
description: str
|
|
55
|
+
map_builder: AnyMapBuilderConfig
|
|
56
|
+
|
|
57
|
+
min_cogs: int = Field(default=1, ge=1)
|
|
58
|
+
max_cogs: int = Field(default=1000, ge=1)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CoGameMission(Config, ABC):
|
|
62
|
+
"""Base class for Mission configurations with common fields and methods."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
description: str
|
|
66
|
+
site: CoGameSite
|
|
67
|
+
num_cogs: int | None = None
|
|
68
|
+
|
|
69
|
+
# Variants are applied to the mission immediately, and to its env when make_env is called
|
|
70
|
+
variants: list[CoGameMissionVariant] = Field(default_factory=list)
|
|
71
|
+
|
|
72
|
+
max_steps: int = Field(default=10000)
|
|
73
|
+
|
|
74
|
+
def __init__(self, **kwargs):
|
|
75
|
+
super().__init__(**kwargs)
|
|
76
|
+
# Can't call `variant.apply` here because it will create a new mission instance
|
|
77
|
+
for variant in self.variants:
|
|
78
|
+
variant.modify_mission(self)
|
|
79
|
+
|
|
80
|
+
def with_variants(self, variants: list[CoGameMissionVariant]) -> Self:
|
|
81
|
+
mission = self
|
|
82
|
+
for variant in variants:
|
|
83
|
+
mission = variant.apply(mission)
|
|
84
|
+
return mission
|
|
85
|
+
|
|
86
|
+
def full_name(self) -> str:
|
|
87
|
+
return f"{self.site.name}{MAP_MISSION_DELIMITER}{self.name}"
|
cogames/docs/SCRIPTED_AGENT.md
CHANGED
|
@@ -261,11 +261,11 @@ uv run cogames play --mission evals.diagnostic_assemble_seeded_search -p baselin
|
|
|
261
261
|
|
|
262
262
|
```bash
|
|
263
263
|
# Run full evaluation suite
|
|
264
|
-
uv run
|
|
264
|
+
uv run cogames diagnose ladybug -S all
|
|
265
265
|
|
|
266
266
|
# Evaluate specific agent
|
|
267
|
-
uv run
|
|
268
|
-
uv run
|
|
267
|
+
uv run cogames diagnose baseline
|
|
268
|
+
uv run cogames diagnose ladybug
|
|
269
269
|
```
|
|
270
270
|
|
|
271
271
|
## Evaluation Results
|
cogames/evaluate.py
CHANGED
|
@@ -13,10 +13,10 @@ from pydantic import BaseModel, ConfigDict
|
|
|
13
13
|
from rich.console import Console
|
|
14
14
|
from rich.table import Table
|
|
15
15
|
|
|
16
|
-
from metta_alo.rollout import run_multi_episode_rollout
|
|
17
16
|
from metta_alo.scoring import allocate_counts, validate_proportions
|
|
18
17
|
from mettagrid import MettaGridConfig
|
|
19
18
|
from mettagrid.policy.policy import PolicySpec
|
|
19
|
+
from mettagrid.runner.rollout import run_multi_episode_rollout
|
|
20
20
|
from mettagrid.simulator.multi_episode.rollout import MultiEpisodeRolloutResult
|
|
21
21
|
from mettagrid.simulator.multi_episode.summary import MultiEpisodeRolloutSummary, build_multi_episode_rollout_summaries
|
|
22
22
|
|
|
@@ -46,6 +46,7 @@ def evaluate(
|
|
|
46
46
|
episodes: int,
|
|
47
47
|
action_timeout_ms: int,
|
|
48
48
|
seed: int = 42,
|
|
49
|
+
device: Optional[str] = None,
|
|
49
50
|
output_format: Optional[Literal["yaml", "json"]] = None,
|
|
50
51
|
save_replay: Optional[str] = None,
|
|
51
52
|
) -> MissionResultsSummary:
|
|
@@ -70,7 +71,7 @@ def evaluate(
|
|
|
70
71
|
all_replay_paths: list[str] = []
|
|
71
72
|
for mission_name, env_cfg in missions:
|
|
72
73
|
counts = allocate_counts(env_cfg.game.num_agents, proportions)
|
|
73
|
-
assignments =
|
|
74
|
+
assignments = [i for i, c in enumerate(counts) for _ in range(c)]
|
|
74
75
|
|
|
75
76
|
progress_label = f"Simulating ({mission_name})"
|
|
76
77
|
with typer.progressbar(length=episodes, label=progress_label) as progress:
|
|
@@ -83,6 +84,7 @@ def evaluate(
|
|
|
83
84
|
max_action_time_ms=action_timeout_ms,
|
|
84
85
|
replay_dir=save_replay,
|
|
85
86
|
create_replay_dir=save_replay is not None,
|
|
87
|
+
device=device,
|
|
86
88
|
on_progress=lambda _episode_idx, _result: progress.update(1),
|
|
87
89
|
)
|
|
88
90
|
|